WPF 位置元素取决于 window 大小

WPF position elements depending on window size

我目前正在开发一个应用程序,用于从 SQL 数据库中检索数据并将其呈现在 UI 中。我顺利地获得了整个功能,但现在我被困在 der GUI 部分。我希望 UI 调整为 window 大小。元素(img、标签、文本框)具有最小高度和最小宽度,但也可以增长到可用的最大值 space。如果 window 变得太小,我希望 UI 像响应式网站一样进行调整。

最大化的 window 会喜欢这样的东西: Maximized window

window 宽度变小,元素相应调整: smaller window

我最好的方法是:

<Grid DataContext="{Binding CurrentPerson}">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="1*"/>
        <ColumnDefinition Width="5*"/>
    </Grid.ColumnDefinitions>

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <Image Grid.Row="0" Source="{Binding Person.Photo}"/>
    </Grid>

    <Viewbox Grid.Column="1" StretchDirection="Both" Stretch="Uniform" HorizontalAlignment="Left" VerticalAlignment="Top">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>

            <Label Grid.Column="0" Grid.Row="0" VerticalAlignment="Center">Title:</Label>
            <Label Grid.Column="0" Grid.Row="1" VerticalAlignment="Center">Name:</Label>
            <Label Grid.Column="0" Grid.Row="2" VerticalAlignment="Center">Street:</Label>
            <Label Grid.Column="0" Grid.Row="3" VerticalAlignment="Center">City:</Label>
            <Label Grid.Column="0" Grid.Row="4" VerticalAlignment="Center">Number:</Label>
            <TextBox Grid.Column="1" Grid.Row="0" HorizontalAlignment="Left" IsReadOnly="True" MinWidth="80"Text="{Binding Person.Title}"/>
            <TextBox Grid.Column="1" Grid.Row="1" HorizontalAlignment="Left" IsReadOnly="True" MinWidth="300">
                <TextBox.Text>
                    <MultiBinding StringFormat="{}{0} {1}">
                        <Binding Path="Person.LastName"/>
                        <Binding Path="Person.FirstName"/>
                    </MultiBinding>
                </TextBox.Text>
            </TextBox>
            <TextBox Grid.Column="1" Grid.Row="2" HorizontalAlignment="Left" IsReadOnly="True" MinWidth="300" Text="{Binding Person.Street}"/>
            <TextBox Grid.Column="1" Grid.Row="3" HorizontalAlignment="Left" IsReadOnly="True" MinWidth="300" Text="{Binding Person.City}"/>
            <TextBox Grid.Column="1" Grid.Row="4" HorizontalAlignment="Left" IsReadOnly="True" MinWidth="30" Text="{Binding Person.Number}"/>
        </Grid>
    </Viewbox>
</Grid>

此解决方案的问题在于,当 window 变得太小时,内容只会缩小以适合 window 并且不再可读。如果图像可以移动到人物数据上方,它将节省很多 space 并且人物数据可以读取。

我试过 wrappanel、viewbox、grid、uniformgrid 等等,但我无法让它按照我想要的方式工作。

非常感谢任何帮助。

提前致谢!

这将涉及某种 C# 代码。您可以编写触发器来更改控件上的 Grid.RowGrid.Column 值,并使用值转换器来决定何时更改,但这更简单。

首先,将主网格分解为两个独立的网格。基本上,这里有两个窗格,所以将它们的内容放在不同的网格中。

<StackPanel x:Name="MainLayout" Orientation="Horizontal">
    <Grid>
        <!-- img -->
    </Grid>

    <Grid>
        <Viewbox Stretch="Uniform">
            <!-- Title, name, etc. -->
        </Viewbox>
    </Grid>
</StackPanel>

给 Window 一个 SizeChanged 处理程序:

private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{
    if (ActualWidth < 400)
    {
        MainLayout.Orientation = Orientation.Vertical;
    }
    else
    {
        MainLayout.Orientation = Orientation.Horizontal;
    }
}

更新

如果您愿意,也可以使用 UniformGrid 来完成。

<UniformGrid x:Name="MainLayout" Columns="2">
    <Grid
        HorizontalAlignment="Left"
        VerticalAlignment="Top"
        >
        <!-- img -->
    </Grid>

    <Viewbox 
        Stretch="Uniform"
        HorizontalAlignment="Left"
        >
        <!-- Title, name, etc. -->
    </Viewbox>
</UniformGrid>

代码隐藏

private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{
    if (ActualWidth < 400)
    {
       //MainLayout.Orientation = Orientation.Vertical;
       MainLayout.Columns = 1;
    }
    else
    {
        //MainLayout.Orientation = Orientation.Horizontal;
        MainLayout.Columns = 2;
    }
}

更新 2

您还可以在右侧窗格中切换网格列和行:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <Grid
        HorizontalAlignment="Left"
        VerticalAlignment="Top"
        >
        <!-- Img -->
    </Grid>

    <Viewbox 
        x:Name="RightPane"
        Grid.Column="1"
        Grid.Row="0"
        Stretch="Uniform" 
        HorizontalAlignment="Left">
        <StackPanel 
            Orientation="Vertical" 
            >
            <!-- Title, name, etc. -->
        </StackPanel>
    </Viewbox>
</Grid>

后面的代码:

private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{
    if (ActualWidth < 400)
    {
        //MainLayout.Orientation = Orientation.Vertical;
        //MainLayout.Columns = 1;
        Grid.SetColumn(RightPane, 0);
        Grid.SetRow(RightPane, 1);
    }
    else
    {
        //MainLayout.Orientation = Orientation.Horizontal;
        //MainLayout.Columns = 2;
        Grid.SetColumn(RightPane, 1);
        Grid.SetRow(RightPane, 0);
    }
}

我想敦促您考虑不使用 Viewbox。将字体和控件缩放到 window 是不寻常的,通常被认为不是很有用。但这是你的项目。

如果您确实想使用 Viewbox,请阅读它的 Stretch 属性,它决定了它如何缩放其内容。

也看看 ViewBox.StretchDirection

作为 Ed 回答的变体,您可以使用值转换器来检查图像的宽度现在是否小于您要应用到它的最小尺寸。所以你最终会得到类似于响应式网页设计中的断点概念的东西。

这篇博客文章解释了这一点: https://www.iambacon.co.uk/blog/a-pattern-for-responsive-applications-in-wpf

如果您的应用程序中有许多 windows 需要此功能,这将特别有用,这样您就可以在所有应用程序中重复使用值转换器。


在更简单的情况下,图像大小是固定的并且不需要使用 window 大小进行调整,您可以简单地使用像这样的 WrapPanel:

<WrapPanel DataContext="{Binding CurrentPerson}">
    <Border BorderBrush="Black" BorderThickness="1">
        <Image Grid.Row="0" Width="200" Height="200" />
    </Border>
    <Grid VerticalAlignment="Center">
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>

        <Label Grid.Column="0" Grid.Row="0" VerticalAlignment="Center">Title:</Label>
        <Label Grid.Column="0" Grid.Row="1" VerticalAlignment="Center">Name:</Label>
        <Label Grid.Column="0" Grid.Row="2" VerticalAlignment="Center">Street:</Label>
        <Label Grid.Column="0" Grid.Row="3" VerticalAlignment="Center">City:</Label>
        <Label Grid.Column="0" Grid.Row="4" VerticalAlignment="Center">Number:</Label>
        <TextBox Grid.Column="1" Grid.Row="0" HorizontalAlignment="Left" IsReadOnly="True" MinWidth="80" Text="{Binding Person.Title}"/>
        <TextBox Grid.Column="1" Grid.Row="1" HorizontalAlignment="Left" IsReadOnly="True" MinWidth="300">
            <TextBox.Text>
                <MultiBinding StringFormat="{}{0} {1}">
                    <Binding Path="Person.LastName"/>
                    <Binding Path="Person.FirstName"/>
                </MultiBinding>
            </TextBox.Text>
        </TextBox>
        <TextBox Grid.Column="1" Grid.Row="2" HorizontalAlignment="Left" IsReadOnly="True" MinWidth="300" Text="{Binding Person.Street}"/>
        <TextBox Grid.Column="1" Grid.Row="3" HorizontalAlignment="Left" IsReadOnly="True" MinWidth="300" Text="{Binding Person.City}"/>
        <TextBox Grid.Column="1" Grid.Row="4" HorizontalAlignment="Left" IsReadOnly="True" MinWidth="30" Text="{Binding Person.Number}"/>
    </Grid>
</WrapPanel>