Question

I'm trying to create a custom control (deriving from Control) with its own drawing logic. The control is simply drawing a diagonal line from the upper-left corner to the right-bottom corner of the control. This logic is based on the control's ActualWidth and ActualHeight, which are only available when rendering the control (AFAIK).

My question is, what is the proper way to do custom drawing efficiently?

There's not much documentation on this, and I'm afraid I'm doing something stupid or unnecessary, like forcing refreshes/redraws every time OnPropertyChanged is fired...

Here is the control's template:

<Style TargetType="{x:Type local:MyCustomControl}">
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type local:MyCustomControl}">
        <Border Background="{TemplateBinding Background}"
                BorderBrush="{TemplateBinding BorderBrush}"
                BorderThickness="{TemplateBinding BorderThickness}">
          <Canvas>
            <Line x:Name="PART_Diagonal"
                  Y1="{Binding Path=DiagonalTop}"
                  Y2="{Binding Path=DiagonalBottom}"
                  X1="{Binding Path=DiagonalLeft}"
                  X2="{Binding Path=DiagonalRight}"
                  Stroke="Red" />
          </Canvas>
        </Border>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

And here is the logic:

public class MyCustomControl : Control, INotifyPropertyChanged
{
  static MyCustomControl()
  {
    DefaultStyleKeyProperty.OverrideMetadata( typeof( MyCustomControl ), new FrameworkPropertyMetadata( typeof( MyCustomControl ) ) );
  }

  public MyCustomControl()
  {
    DataContext = this;
  }

  protected override void OnRenderSizeChanged( SizeChangedInfo sizeInfo )
  {
    base.OnRenderSizeChanged( sizeInfo );

    var margin = 25d;

// These calculations are based on the control's current size.
    DiagonalTop = margin;
    DiagonalBottom = ActualHeight - margin;
    DiagonalLeft = margin;
    DiagonalRight = ActualWidth - margin;

// Is this forcing redraws (i.e. invalidating the layout)?
    OnPropertyChanged( "DiagonalTop" );
    OnPropertyChanged( "DiagonalBottom" );
    OnPropertyChanged( "DiagonalLeft" );
    OnPropertyChanged( "DiagonalRight" );
  }

  public double DiagonalTop { get; private set; }
  public double DiagonalBottom{ get; private set; }
  public double DiagonalLeft { get; private set; }
  public double DiagonalRight { get; private set; }

  public event PropertyChangedEventHandler PropertyChanged;
  protected void OnPropertyChanged(string propertyname)
  {
    if (PropertyChanged != null)
      PropertyChanged.Invoke( this, new PropertyChangedEventArgs( propertyname ) );
  }
}
Was it helpful?

Solution

You could do it much easier, without any additional properties. Simply use the Padding property for the inner spacing:

<ControlTemplate TargetType="{x:Type local:MyCustomControl}">
    <Border Background="{TemplateBinding Background}" 
            BorderBrush="{TemplateBinding BorderBrush}" 
            BorderThickness="{TemplateBinding BorderThickness}"
            Padding="{TemplateBinding Padding}">
        <Canvas Name="DrawingCanvas">
            <Line X1="0" X2="{Binding ActualWidth, ElementName=DrawingCanvas}" 
                  Y1="0" Y2="{Binding ActualHeight, ElementName=DrawingCanvas}" 
                  Stroke="Red" />
        </Canvas>
    </Border>
</ControlTemplate>

Use your control with Padding:

<local:MyCustomControl Padding="25" ... />
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top