Поддержание линий фиксированной толщины в WPF с масштабированием/растяжением Viewbox

у меня есть <Grid>, который содержит вертикальные и горизонтальные <Line>s. Я хочу, чтобы сетка была масштабируемой с размером окна и сохраняла соотношение сторон, поэтому она содержится в <Viewbox Stretch="Uniform">.

однако я также хочу, чтобы линии всегда отображались шириной 1 пиксель, поэтому я использую:

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

это делает начальный внешний вид линий идеальным, но как только вы начинаете изменять размер окна, начинается растяжка / масштабирование, и линии становятся смесью 1 и снова 2 пикселя толщиной.

есть ли способ, чтобы линии всегда были толщиной 1 пиксель, а также позволяли изменять размер окна/сетки?

обновление-использование геометрии пути в соответствии с предложением Клеменса

@Clemens-Спасибо за выделение различий в рендеринге между линиями и путями. Когда я пытаюсь переработать свой код, используя Ваш пример, у меня возникает ощущение, что я копаю больше ям для себя и не очень понимаю вся концепция (полностью моя вина, а не ваша, я просто новичок в WPF).

Я добавлю несколько скриншотов, чтобы проиллюстрировать следующее описание:

Я делаю игровую доску (для игры Go, в случае, если это помогает понять макет вообще). У меня есть сетка 9x9, и я планирую разместить игровые фигуры, просто добавив эллипс к определенной ячейке сетки.

чтобы нарисовать основные линии на доске, однако, мне нужно нарисовать линии пересечение середины ячеек по всей доске (в Go фигуры размещаются на пересечениях, а не в середине ячеек).

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

вот как я это сделал до сих пор (я добавляю пути программно, из-за того, как вычисляются координаты. Не уверен, что это может все быть сделано в 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

это в значительной степени, как я хочу, чтобы это выглядело. Это вызвало еще 2 вопроса для меня:

1) я принимаю правильный подход, где я вычисляю x1, x2, y1 и y2 значения, погружая их по общей ширине платы, чтобы создать число между 0 и 1, так что ScaleTransform может быть применен к ним?

2) Теперь, когда я не использую Viewbox больше, как мне выполнить масштабирование с фиксированным коэффициентом? Если я увеличу свое окно, доска растянется непропорционально (см. изображение ниже). (Это больше не анти-псевдонимы строк, хотя, что здорово.)

Stretched board loses aspect ratio

Я знаю, что это становится немного монолитным постом. Я очень благодарен за ваше терпение и ответы.

1 ответов


видовое окно может только" визуально " масштабировать дочерний элемент, включая толщину любого отображаемого штриха. Вам нужно масштабирование, которое применяется только к геометрии линий (или других фигур), но не влияет на ход.

вместо Line объекты, вы можете нарисовать свои линии по Path объекты, которые используют transformed 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>

для того чтобы получить равномерное масштабирование вы можете просто связать оба ScaleTransform по ScaleX и ScaleY "свойства" либо 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;
}