使用 Viewbox 缩放/拉伸在 WPF 中保持固定粗细线

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15320099/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 07:59:51  来源:igfitidea点击:

Maintaining fixed-thickness lines in WPF with Viewbox scaling/stretching

c#wpfantialiasingviewbox

提问by Twicetimes

I have a <Grid>which contains some vertical and horizontal <Line>s. I want the grid to be scalable with the window size, and retain its aspect ratio, so it's contained in a <Viewbox Stretch="Uniform">.

我有一个<Grid>包含一些垂直和水平的<Line>s。我希望网格可以根据窗口大小进行缩放,并保持其纵横比,因此它包含在<Viewbox Stretch="Uniform">.

However, I also want the lines to always render with a width of 1 pixel, so I use:

但是,我还希望线条始终以 1 像素的宽度呈现,因此我使用:

Line line = new Line();
line.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
// other line settings here...

This makes the lines' initial appearance ideal, but as soon as you start resizing the window, the stretching/scaling kicks in, and the lines become a mixture of 1 and 2 pixels thick again.

这使得线条的初始外观非常理想,但是一旦您开始调整窗口大小,拉伸/缩放就会开始,线条再次变成 1 和 2 像素粗的混合。

Is there any way to have the lines always be 1 pixel thick and also allow for resizing of the window/grid?

有什么方法可以让线条始终为 1 像素厚,并且还允许调整窗口/网格的大小?

Update - Using path geometry as per Clemens' suggestion

更新 - 根据 Clemens 的建议使用路径几何

@Clemens - Thanks for highlighting the rendering differences between lines and paths. As I try to rework my code using your example, I'm getting that sinking feeling that I'm digging more holes for myself and not really grasping the entire concept (entirely my fault, not yours, I'm just new to WPF).

@Clemens - 感谢您强调线条和路径之间的渲染差异。当我尝试使用您的示例重新编写我的代码时,我有一种下沉的感觉,即我正在为自己挖掘更多漏洞并且没有真正掌握整个概念(完全是我的错,不是您的错,我只是 WPF 的新手) .

I'll add some screenshots to illustrate the following description:

我将添加一些屏幕截图来说明以下描述:

I'm making a game board (for the game of Go, in case that helps understand the layout at all). I have a 9x9 grid, and I'm planning on placing the game pieces by simply adding an ellipse to a particular grid cell.

我正在制作一个游戏板(用于围棋游戏,以防有助于理解布局)。我有一个 9x9 的网格,我打算通过简单地向特定网格单元添加一个椭圆来放置游戏块。

To draw the underlying lines on the board, however, I need to draw lines intersecting the middle of the cells across the board (in Go, pieces are placed on the intersections, not the middle of the cells).

然而,要在板上绘制底层线,我需要在板上绘制与单元格中间相交的线(在 Go 中,棋子放置在交叉点上,而不是单元格的中间)。

It could well be that I'm taking entirely the wrong approach, please feel free to tell me to start again down a different route, rather than hacking around within the current structure.

很可能我采取了完全错误的方法,请随时告诉我沿着不同的路线重新开始,而不是在当前结构内进行黑客攻击。

This is how I've done it so far (I'm adding the paths programatically, due to the way the coordinates are calculated. Not sure if it can all be done in XAML):

这就是我到目前为止所做的(由于坐标的计算方式,我正在以编程方式添加路径。不确定是否可以在 XAML 中完成):

XAML:

XAML:

<Grid MinHeight="400" MinWidth="400" ShowGridLines="False" x:Name="boardGrid">
    <Grid.Resources>
        <ScaleTransform x:Key="transform"
            ScaleX="{Binding ActualWidth, ElementName=boardGrid}"
            ScaleY="{Binding ActualHeight, ElementName=boardGrid}" />
    </Grid.Resources>
    <Grid.RowDefinitions>
        <RowDefinition></RowDefinition>
        <!-- more rows, 9 in total -->
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition></ColumnDefinition>
        <!-- more columns, 9 in total -->
    </Grid.ColumnDefinitions>

    <!-- example game pieces -->
    <Ellipse Stroke="Black" Fill="#333333" Grid.Row="3" Grid.Column="2" />
    <Ellipse Stroke="#777777" Fill="#FFFFFF" Grid.Row="4" Grid.Column="4" />
</Grid>

C#:

C#:

int cols = 9;
int rows = 9;

// Draw horizontal lines
for (int row = 0; row < rows; row++)
{
    var path = new System.Windows.Shapes.Path();
    path.Stroke = Brushes.Black;
    path.StrokeThickness = 1;
    path.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
    Grid.SetRow(path, row);
    Grid.SetColumnSpan(path, cols);
    Grid.SetZIndex(path, -1);

    double cellWidth = boardGrid.ColumnDefinitions[0].ActualWidth;
    double cellHeight = boardGrid.RowDefinitions[0].ActualHeight;
    double x1 = (cellWidth / 2) / boardGrid.ActualWidth;
    double y1 = (cellHeight / 2) / boardGrid.ActualHeight;
    double x2 = ((cellWidth * cols) - (cellWidth / 2)) / boardGrid.ActualWidth;
    double y2 = (cellHeight / 2) / boardGrid.ActualHeight;

    path.Data = new LineGeometry(new Point(x1, y1),
                                 new Point(x2, y2), 
                           (ScaleTransform)boardGrid.TryFindResource("transform"));
    boardGrid.Children.Add(path);
}

// Similar loop code follows for vertical lines...

This is what I get when using the code above

这是我在使用上面的代码时得到的

Result when using paths

使用路径时的结果

This is pretty much how I want it to look. It's raised 2 more questions for me:

这几乎就是我想要的样子。它为我提出了另外两个问题:

1) Am I taking the right approach where I'm calculating the x1, x2, y1and y2values by diving them by the total board width to create a number between 0 and 1, so that the ScaleTransform can then be applied to them?

1)我是否采取正确的方法,即我计算x1x2y1y2的值由潜水它们由总宽度板创建0和1之间的数,使得ScaleTransform然后可以应用于它们?

2) Now that I'm not using a Viewboxany more, how do I accomplish fixed-ratio scaling? If I enlarge my window, the board stretches out of proportion (see image below). (It doesn't anti-alias the lines any more though, which is great.)

2) 现在我不再使用 aViewbox了,我如何完成固定比例缩放?如果我放大我的窗口,板会不成比例地伸展(见下图)。(不过,它不再对线条进行抗锯齿处理,这很棒。)

Stretched board loses aspect ratio

拉伸板失去纵横比

I know this is getting to be a bit of a monolithic post. I'm very grateful for your patience and responses.

我知道这有点像一个单一的帖子。我非常感谢您的耐心和回复。

回答by Clemens

A Viewbox can only "visually" scale its child element, including the thickness of any rendered stroke. What you need is a scaling that only applies to the geometry of the lines (or other shapes), but leaves the stroke unaffected.

Viewbox 只能“在视觉上”缩放其子元素,包括任何渲染笔画的粗细。您需要的是仅适用于线条(或其他形状)的几何形状的缩放,但不影响笔画。

Instead of using Lineobjects, you could draw your lines by Pathobjects that use transformed LineGeometriesfor their Dataproperty. You could create a ScaleTransformthat scales from logical coordinates to viewport coordinates by using the Grid's width and height as scaling factors in x and y direction. Each LineGeometry (or any other Geometry) would use logical coordinates in the range 0..1:

Line您可以不使用对象,而是通过Path使用转换后的LineGeometries作为其Data属性的对象绘制线条。ScaleTransform通过使用网格的宽度和高度作为 x 和 y 方向的缩放因子,您可以创建从逻辑坐标缩放到视口坐标的一个。每个 LineGeometry(或任何其他几何图形)将使用 0..1 范围内的逻辑坐标:

<Grid x:Name="grid">
    <Grid.Resources>
        <ScaleTransform x:Key="transform"
                        ScaleX="{Binding ActualWidth, ElementName=grid}"
                        ScaleY="{Binding ActualHeight, ElementName=grid}"/>
    </Grid.Resources>
    <Path Stroke="Black" StrokeThickness="1">
        <Path.Data>
            <LineGeometry StartPoint="0.1,0.1" EndPoint="0.9,0.9"
                          Transform="{StaticResource transform}"/>
        </Path.Data>
    </Path>
</Grid>


In order to get a uniform scaling you may simply bind both the ScaleTransform's ScaleXand ScaleYproperties to either the ActualWidthor ActualHeightof the Grid:

为了获得统一的缩放比例,您可以简单地将 ScaleTransform 的ScaleXScaleY属性绑定到Grid的ActualWidthActualHeight

<Grid x:Name="grid">
    <Grid.Resources>
        <ScaleTransform x:Key="transform"
                        ScaleX="{Binding ActualWidth, ElementName=grid}"
                        ScaleY="{Binding ActualWidth, ElementName=grid}"/>
    </Grid.Resources>
    ...
</Grid>

You may also calculate the uniform scaling factor from the minimum value of the width and height, with a bit of code behind:

您也可以从宽度和高度的最小值计算统一缩放因子,后面有一些代码:

<Grid x:Name="grid" SizeChanged="grid_SizeChanged">
    <Grid.Resources>
        <ScaleTransform x:Key="transform"/>
    </Grid.Resources>
    ...
</Grid>

with a SizeChanged handler like this:

使用像这样的 SizeChanged 处理程序:

private void grid_SizeChanged(object sender, SizeChangedEventArgs e)
{
    var transform = grid.Resources["transform"] as ScaleTransform;
    var minScale = Math.Min(grid.ActualWidth, grid.ActualHeight);
    transform.ScaleX = minScale;
    transform.ScaleY = minScale;
}