Square WPF user control

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

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s