2016-12-07 68 views
0

我正在處理一個自定義的WPF控件,該控件應該可視化可滾動區域中的數千個圖形基元。該控件的模板的核心部分如下:WPF中DrawingContext的奇怪繪圖

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

爲了提高性能,所有的繪圖操作在ItemAreaElement的方法的OnRender進行。有清脆的圖紙,我也用以下設置在初始化代碼:

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的筆記本電腦配備了超高清屏幕,282ppi(系統比例係數爲300%),啓動此代碼,我得到這樣的畫面:

enter image description here

或者,在paint.net與網格線變焦後:

enter image description here

如您所見,我的ItemAreaElement的左邊和頂邊部分由控件的邊框覆蓋。一定是這樣嗎?有沒有我可以用來避免這種情況的設置?

第二個問題是行不包括起點(請參閱我的「單元格」的左上角)。這是預期的行爲?如果是這樣,如何強制WPF繪製起始像素?

第三個問題是綠線應該符合的位置或與設備無關的點(我的細胞的右下角)。正如你所看到的,這一點是鋸齒狀的。我期望在那個地方看到一個綠色的廣場。我可以在DrawingContext.DrawLine方法的幫助下實現這個嗎?或者,我是否需要使用更復雜的幾何體以及多點線等的特殊設置?

順便說一句,當我在具有「經典」96 ppi顯示器並且操作系統的比例因子設置爲100%的測試電腦上啓動此代碼時,右下角的情況稍好一些:

enter image description here

但我甚至看不到頂行或垂直紅線水平紅線在第一列。我希望在那裏看到他們,但不被控制邊界覆蓋。如果你知道如何解決所有這些問題,請告訴我。

+1

要知道,WPF使用矢量圖形,其中座標是不是像素,但浮點「設備無關的單位」(類型爲' double')。因此,在位置'(x,y)'處填充一個像素需要在'(x + 0.5,y + 0.5)'處用'厚度'設置爲'1.0'的筆繪製零長度線, StartLineCap'和'EndLineCap'設置爲'Square'。 – Clemens

+0

@Clemens,我知道WPF的這種性質。這在WPF教程中的[this one](https://www.wpftutorial.net/DrawOnPhysicalDevicePixels.html)等許多文章中有解釋(請參閱'對齊邊緣而不是中心點'部分)。你認爲這將適用於任何屏幕分辨率和系統比例因子嗎? – 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; 
    } 
}