In this post I would like to provide code for simple Wpf user control that fills as much space as possible but preserve square form. For demonstration I used 3D-look ellipse described in this post. Full code is accessible on GitHub Blog repository.
Described control looks in the following way:
Size of ellipse is controlled by multi-value converter that return minimum of input values:
<UserControl.Resources> <converters:MinDoubleConverter x:Key="SizeConverter"/> <UserControl.Resources>
and width and height of the ellipse are bound to actual height and actual width of the control:
<Ellipse.Width>
<MultiBinding Converter="{StaticResource SizeConverter}" ConverterParameter="2" FallbackValue="20">
<Binding ElementName="MainPanel" Path="ActualWidth" Mode="OneWay"/>
<Binding ElementName="MainPanel" Path="ActualHeight" Mode="OneWay"/>
</MultiBinding>
</Ellipse.Width>
<Ellipse.Height>
<MultiBinding Converter="{StaticResource SizeConverter}" ConverterParameter="2" FallbackValue="20">
<Binding ElementName="MainPanel" Path="ActualWidth" Mode="OneWay"/>
<Binding ElementName="MainPanel" Path="ActualHeight" Mode="OneWay"/>
</MultiBinding>
</Ellipse.Height>
Converter takes an array of input values, converts them to doubles, calculates minimum and rounds the result if converter parameter is an integer.
/// <summary>
/// Returns minimal value of input double values.
/// </summary>
/// <summary>
/// Returns minimal value of input double values.
/// </summary>
[ValueConversion(typeof(double[]), typeof(double))]
public class MinDoubleConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType,
object parameter, CultureInfo culture)
{
Debug.Assert(targetType.IsAssignableFrom(typeof(double)),
$"targetType should be {typeof(double).FullName}");
// values:
if (values == null || values.Length <= 0)
return DependencyProperty.UnsetValue;
var tValues = Convert(values, culture);
// check that all input values were not wrong
if (tValues.Count <= 0)
return DependencyProperty.UnsetValue;
// return aggregate value
var tResult = tValues.Min();
// check parameter and round resulting value
var roundDigits = 0;
if (Convert(parameter, culture,
(value1, culture1) => Math.Max(0, System.Convert.ToInt32(value1)),
ref roundDigits))
{
tResult = Math.Round(tResult, roundDigits);
}
return System.Convert.ChangeType(tResult, targetType);
}
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter, CultureInfo culture)
{
// no back conversion
return null;
}
private static IList<double> Convert(object[] values, CultureInfo culture)
{
var tValues = new List<double>(values.Length);
foreach (var value in values)
{
double result = 0;
if (Convert(value, culture,
(value1, culture1) =>
{
var result1 = System.Convert.ToDouble(value1);
return double.IsNaN(result1) ? 0 : result1;
},
ref result))
{
tValues.Add(result);
}
}
return tValues;
}
private static bool Convert<T>(object value, CultureInfo culture,
Func<object, CultureInfo, T> convertFunc, ref T tResult)
{
try
{
if (value == null || value.Equals(DependencyProperty.UnsetValue))
return false;
tResult = convertFunc(value, culture);
}
catch (FormatException ex)
{
// ignore it, some wrong input value
Debug.Assert(false, DebugMessage<T>(value, ex));
}
catch (InvalidCastException ex)
{
// ignore it, some wrong input value
Debug.Assert(false, DebugMessage<T>(value, ex));
}
catch (OverflowException ex)
{
// ignore it, some wrong input value
Debug.Assert(false, DebugMessage<T>(value, ex));
}
return true;
}
private static string DebugMessage<T>(object value, Exception ex)
{
return
$"value \"{value?.ToString() ?? "null"}\", type {value?.GetType().FullName ?? "null"}" +
$" should be convertible to type {typeof(T).FullName}. Exception: {ex.Message}";
}
}
1. All used IP-addresses, names of servers, workstations, domains, are fictional and are used exclusively as a demonstration only.
2. Information is provided «AS IS».