Сохранение линий фиксированной толщины в WPF с масштабированием / растяжением окна просмотра

У меня есть <Grid>, который содержит несколько вертикальных и горизонтальных <Line>. Я хочу, чтобы сетка масштабировалась в соответствии с размером окна и сохраняла свое соотношение сторон, поэтому она содержится в <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...

Это то, что я получаю при использовании приведенного выше кода

Результат при использовании путей

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

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

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

Растянутая доска теряет соотношение сторон

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


person Twicetimes    schedule 10.03.2013    source источник
comment
выложите пожалуйста скриншот.   -  person Federico Berasategui    schedule 10.03.2013
comment
Отредактировал вопрос и добавил скриншоты.   -  person Twicetimes    schedule 11.03.2013
comment
См. Мой отредактированный ответ о том, как добиться равномерного масштабирования.   -  person Clemens    schedule 11.03.2013


Ответы (1)


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

Вместо использования объектов Line вы можете рисовать линии с помощью объектов Path, которые использовать преобразованные LineGeometries для их _ 3_. Вы можете создать 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>

Чтобы получить равномерное масштабирование, вы можете просто привязать свойства ScaleX и ScaleY ScaleTransform к 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;
}
person Clemens    schedule 10.03.2013
comment
Спасибо за разъяснение различий в рендеринге геометрии. Я переработал свой код, используя Paths, и отредактировал свой исходный вопрос с моими выводами. - person Twicetimes; 11.03.2013
comment
Привязки к ScaleTransform на месте, и я реализовал обработчик SizeChanged, но при изменении размера окна сетка по-прежнему растягивается без фиксированного соотношения. - person Twicetimes; 11.03.2013
comment
Я думал, что мы говорим о том, как сохранить толщину линий, а не макет в целом. В любом случае, чтобы сохранить квадрат сетки, вы можете, например, связать его высоту с его фактической шириной. Конечно, есть несколько способов добиться этого, в зависимости от вашего общего макета. - person Clemens; 11.03.2013
comment
Да, теперь это кажется очевидным :) Спасибо. - person Twicetimes; 12.03.2013