2016-12-07 11 views
0

Я работаю над настраиваемым элементом управления WPF, который должен визуализировать тысячи графических примитивов в области с прокруткой. Основной частью шаблона элемента управления состоит в следующем:Странный рисунок с DrawingContext в WPF

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

Для повышения производительности, все операции рисования выполняются в методе OnRender в ItemAreaElement. Для того, чтобы иметь четкий рисунок, я использую следующие настройки в коде инициализации:

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

Однако, у меня есть какие-то странные проблемы с моим рисунком. Чтобы продемонстрировать их, я упростил определение моего ItemAreaElement на следующее:

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; 
     } 
    } 
} 

Когда я запускаю этот код на мой основной Dev ноутбук, снабженный Ultra-HD экран с 282ppi (коэффициент системы масштаб 300%) , я получаю эту картину:

enter image description here

Или, после масштабирования в Paint.NET с сеткой:

enter image description here

Как вы видите, левый и верхний края элемента ItemAreaElement частично покрыты границей элемента управления. Должно быть так? Есть ли способ, который я могу использовать, чтобы избежать этого?

Вторая проблема - это строки, которые не включают начальную точку (см. Верхний левый угол моих «ячеек»). Это ожидаемое поведение? ЕСЛИ так, как заставить WPF нарисовать начальный пиксель?

И третья проблема - это место или независимая от устройства точка, в которой должны встречаться зеленые линии (нижний правый угол моих ячеек). Как вы можете видеть, этот момент зазубрен. Я ожидал увидеть только зеленую площадь в этом месте. Могу ли я реализовать это с помощью метода DrawingContext.DrawLine? Или мне нужно использовать более сложную геометрию со специальными настройками для многоточечных линий и т. Д.?

BTW, когда я запускаю этот код на тестовом ПК с «классическим» 96-пиксельным монитором и масштабным коэффициентом ОС, установленным на 100%, в нижнем правом углу ситуация немного лучше:

enter image description here

Но я даже не видите горизонтальные красные линии в верхнем ряду или вертикальных красных линий в первом столбце. Я ожидал увидеть их там, но не должен быть охвачен границей контроля. Если вы знаете, как исправить все эти проблемы, скажите мне, пожалуйста.

+1

Имейте в виде, что WPF использует векторную графику, где координаты не являются пикселями, но с плавающей точкой «устройство независимых единиц» (типа ' double'). Поэтому «заполнение» пикселя в позиции «(x, y)» потребует рисовать линию нулевой длины на '(x + 0,5, y + 0,5)' с помощью Pen с 'Thickness', установленным в' 1.0' и ' StartLineCap' и 'EndLineCap' установлены на' Квадрат'. – Clemens

+0

@Clemens, я знаю об этом характере WPF. Это объясняется во многих статьях, таких как [этот] (https://www.wpftutorial.net/DrawOnPhysicalDevicePixels.html) в учебнике WPF (см. Раздел «Выровнять края, а не центральные точки»). Считаете ли вы, что это будет работать для любого разрешения экрана и коэффициента масштабирования системы? – TecMan

+0

Несомненно, вам нужно иметь в виду, где находится начало координат. Если вы, например, нарисуйте горизонтальную линию на 'y = 0' (и управляющие клипсы на ее границу), вы увидите только нижнюю половину строки. – Clemens

ответ

0

Мне удалось решить все мои проблемы, установив соответствующие рекомендации. Ниже вы найдете улучшенную версию методы OnRender() представлены выше:

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; 
    } 
}