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».