使用Viewbox缩放/拉伸在WPF中维护固定厚度的线条

9
我有一个包含一些垂直和水平线的<Grid>。我希望网格随着窗口大小的变化而可缩放,并保持其纵横比,因此将其包含在一个<Viewbox Stretch="Uniform">中。
然而,我也希望线始终以1像素的宽度呈现,因此我使用:
Line line = new Line();
line.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
// other line settings here...

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

有没有办法使线始终保持1像素粗,并允许调整窗口/网格大小?

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

@Clemens - 感谢您强调线条和路径之间的渲染差异。 当我尝试使用您的示例重新编写代码时,我感到沮丧,因为我在为自己挖更多的坑而不是真正掌握整个概念(这完全是我的错,而不是你的错,我只是WPF的新手)。

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

我正在制作一个游戏板(用于Go游戏,以帮助理解布局)。 我有一个9x9的网格,我计划通过将椭圆添加到特定的网格单元来放置游戏棋子。

为了在棋盘上绘制底层线条,我需要在整个棋盘上绘制穿过单元格中心的线条(在围棋中,棋子放置在交叉点上,而不是单元格中心)。
可能我正在采取完全错误的方法,请随时告诉我从不同的路线重新开始,而不是在当前结构内进行修改。
到目前为止,我是这样做的(由于坐标计算的方式,我正在以编程方式添加路径。不确定是否可以全部在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#:

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

使用以上代码后,我得到了这个结果

Result when using paths

这基本上就是我想要的样子。它让我产生了两个问题:

1) 我这样计算 x1, x2, y1y2 值的方法正确吗?将它们除以整个板宽以创建一个介于 0 和 1 之间的数字,以便可以应用 ScaleTransform?

2) 现在我不再使用 Viewbox,如何实现固定比例缩放?如果我放大窗口,棋盘的比例就会失调(请参见下面的图像)。 (不过,线条不再有抗锯齿效果,这很好。)

Stretched board loses aspect ratio

我知道这篇文章有点冗长。非常感谢您的耐心和回复。


请发布一张截图。 - Federico Berasategui
我已经编辑了问题并添加了截图。 - Twicetimes
请查看我编辑后的答案,了解如何实现统一缩放。 - Clemens
1个回答

11

一个视口(Viewbox)只能“视觉上”缩放其子元素,包括任何渲染轮廓的厚度。您需要的是一种只应用于线条(或其他形状)的几何而不影响轮廓的缩放。

您可以通过使用转换的LineGeometries作为Data属性的Path对象来绘制线条,而不是使用Line对象。 您可以创建一个ScaleTransform,通过使用网格的宽度和高度作为x和y方向的缩放因子,以逻辑坐标到视口坐标的缩放。每个LineGeometry(或任何其他Geometry)将使用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>
为了实现统一的缩放,您可以简单地将ScaleTransform的ScaleX和ScaleY属性绑定到Grid的ActualWidth或ActualHeight之一:
<Grid x:Name="grid">
    <Grid.Resources>
        <ScaleTransform x:Key="transform"
                        ScaleX="{Binding ActualWidth, ElementName=grid}"
                        ScaleY="{Binding ActualWidth, ElementName=grid}"/>
    </Grid.Resources>
    ...
</Grid>

你也可以通过一些代码,从宽度和高度的最小值计算出统一的缩放因子:

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

使用以下代码作为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;
}

感谢您澄清几何渲染的差异。我已经使用路径重新编写了我的代码,并在原始问题中编辑了我的发现。 - Twicetimes
比例变换的绑定已经就位,我已经实现了SizeChanged处理程序,但是在调整窗口大小时,网格仍然会拉伸而没有固定的比例。 - Twicetimes
我认为我们正在讨论如何保持线条粗细,而不是一般的布局。无论如何,为了保持网格正方形,您可以将其高度绑定到其实际宽度,例如。当然,有多种方法可以实现这一点,具体取决于您的整体布局。 - Clemens
是的,现在你这么说,这似乎很明显 :) 谢谢。 - Twicetimes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接