Tecman
Tecman

Reputation: 3009

Strange drawing with DrawingContext

I'm working on a custom WPF control that should visualize thousands of graphical primitives in a scrollable area. The core part of the control's template is the following:

<Setter Property="Template">
    <Setter.Value>
        <ControlTemplate TargetType="{x:Type local:ItemVisualizer}">
            <Border Background="{TemplateBinding Background}"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}">
        
      <Grid>
        <Grid.RowDefinitions>
          <RowDefinition Height="*" />
          <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="*" />
          <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        <local:ItemAreaElement Grid.Row="0" Grid.Column="0" x:Name="PART_ItemArea" />
        <ScrollBar Grid.Row="0" Grid.Column="1" x:Name="PART_ScrollBarVert" Orientation="Vertical" Maximum="100" />
        <ScrollBar Grid.Row="1" Grid.Column="0" x:Name="PART_ScrollBarHorz" Orientation="Horizontal" Maximum="100" />
        <Rectangle Grid.Row="1" Grid.Column="1" x:Name="PART_SizeGrip" Focusable="False" Fill="#F0F0F0" />
      </Grid>
        
            </Border>
        </ControlTemplate>
    </Setter.Value>
</Setter>

For better performance, all drawing operations are performed in the OnRender method of the ItemAreaElement. To have crisp drawing, I also use the following setting in the initialization code:

this.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);

However, I have some strange issues with my drawing. To demonstrate them, I simplified the definition of my ItemAreaElement to the following:

class ItemAreaElement : FrameworkElement
{
  protected override void OnRender(DrawingContext drawingContext)
  {
    base.OnRender(drawingContext);

    const int ITEM_WIDTH = 60;
    const int ITEM_HEIGHT = 20;

    Pen penRed = new Pen(Brushes.Red, 1);
    Pen penGreen = new Pen(Brushes.Green, 1);

    int y = 0;
    for (int iRow = 0; iRow < 3; iRow++)
    {
      int x = 0;
      for (int iCol = 0; iCol < 2; iCol++)
      {
        Point cornerTopLeft = new Point(x, y);
        dc.DrawLine(penRed, cornerTopLeft, new Point(x + ITEM_WIDTH - 1, y));
        dc.DrawLine(penRed, cornerTopLeft, new Point(x, y + ITEM_HEIGHT - 1));

        Point cornerBottomRight = new Point(x + ITEM_WIDTH - 1, y + ITEM_HEIGHT - 1);
        dc.DrawLine(penGreen, new Point(x + ITEM_WIDTH - 1, y), cornerBottomRight);
        dc.DrawLine(penGreen, new Point(x, y + ITEM_HEIGHT - 1), cornerBottomRight);

        x += ITEM_WIDTH;
      }
      y += ITEM_HEIGHT;
    }
  }
}

When I launch this code on my main dev laptop equipped with an Ultra-HD screen with 282ppi (the system scale factor is 300%), I get this picture:

enter image description here

Or, after zooming in paint.net with gridlines:

enter image description here

As you see, the left, and top edges of my ItemAreaElement are partially covered by the border of the control. Must it be so? Is there a setting I can use to avoid this?

The second problem are lines that do not include the start point (see the top-left corner of my "cells"). Is this the expected behavior? IF so, how to force WPF to draw the start pixel?

And the third problem is the place or device-independent point in which the green lines should meet (the bottom-right corner of my cells). As you can see, this point is jagged. I expected to see just a green square in that place. Can I implement this with the help of the DrawingContext.DrawLine method? Or do I need to use a more complex geometry with special settings for multi-point lines, etc.?

By the way, when I launch this code on a test pc with a "classic" 96 ppi monitor and the scale factor of the OS set to 100%, the situation is a little bit better in the bottom-right corner:

enter image description here

But I even do not see the horizontal red lines in the top row or vertical red lines in the first column. I expected to see them there but not to be covered by the control's border.

Upvotes: 1

Views: 789

Answers (1)

Tecman
Tecman

Reputation: 3009

I've managed to solve all my problems by setting the corresponding guidelines. Below you'll find the improved version of the OnRender() method presented above:

protected override void OnRender(DrawingContext dc)
{
    base.OnRender(dc);

    const int ITEM_WIDTH = 60;
    const int ITEM_HEIGHT = 20;

    const double sizeOfPen = 1;
    double halfSizeOfPen = sizeOfPen / 2.0;

    Pen penRed = new Pen
    {
        Brush = Brushes.Red,
        Thickness = sizeOfPen,
        StartLineCap = PenLineCap.Square,
        EndLineCap = PenLineCap.Square
    };
    Pen penGreen = new Pen
    {
        Brush = Brushes.Green,
        Thickness = sizeOfPen,
        StartLineCap = PenLineCap.Square,
        EndLineCap = PenLineCap.Square
    };

    int y = 0;
    for (int iRow = 0; iRow < 3; iRow++)
    {
        int x = 0;
        for (int iCol = 0; iCol < 2; iCol++)
        {
            GuidelineSet guidelines = new GuidelineSet();
            guidelines.GuidelinesX.Add(x);
            guidelines.GuidelinesX.Add(x + ITEM_WIDTH);
            guidelines.GuidelinesY.Add(y);
            guidelines.GuidelinesY.Add(y + ITEM_HEIGHT);

            dc.PushGuidelineSet(guidelines);

            Point cornerTopLeft = new Point(x + halfSizeOfPen, y + halfSizeOfPen);
            dc.DrawLine(penRed, cornerTopLeft, new Point(x + ITEM_WIDTH - halfSizeOfPen, y + halfSizeOfPen));
            dc.DrawLine(penRed, cornerTopLeft, new Point(x + halfSizeOfPen, y + ITEM_HEIGHT - halfSizeOfPen));

            Point cornerBottomRight = new Point(x + ITEM_WIDTH - halfSizeOfPen, y + ITEM_HEIGHT - halfSizeOfPen);
            dc.DrawLine(penGreen, new Point(x + ITEM_WIDTH - halfSizeOfPen, y + halfSizeOfPen), cornerBottomRight);
            dc.DrawLine(penGreen, new Point(x + halfSizeOfPen, y + ITEM_HEIGHT - halfSizeOfPen), cornerBottomRight);

            dc.Pop();

            x += ITEM_WIDTH;
        }
        y += ITEM_HEIGHT;
    }
}

Upvotes: 1

Related Questions