Поддържане на линии с фиксирана дебелина в WPF с мащабиране/разтягане на Viewbox

Имам <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)


Viewbox може само "визуално" да мащабира дъщерния си елемент, включително дебелината на всеки изобразен щрих. Това, от което се нуждаете, е мащабиране, което се прилага само към геометрията на линиите (или други форми), но оставя щриха незасегнат.

Вместо да използвате 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>

За да получите равномерно мащабиране, можете просто да свържете свойствата 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