3

I am currently working on an app to retrieve data from an SQL database and present it in the UI. I got the whole functionality runnig smoothly but now I'm stuck at der GUI part. I want the UI to adjust to the window size. The elements (img, labels, textboxes) have a minheight and minwidth but can also grow to the maximum available space. If the window gets too small, I want the UI to adjust like a responsive website.

The maximized window would like something like this: Maximized window

The window width got to small & the elements adjust accordingly: smaller window

My best approach was:

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

The problem with this solution is that when the window gets too small, the content just shrinks to fit inside the window and isn't readable anymore. If the image could move above the person data it would save a lot of space an the person data could be readable.

I played around with wrappanel, viewbox, grid, uniformgrid and so on but I couldn't get it to work the way I want it to.

Any help is very much appreciated.

Thanks in advance!

bego
  • 99
  • 3
  • 9

2 Answers2

5

This is going to involve some kind of C# code. You could write triggers that change Grid.Row and Grid.Column values on controls and use a value converter to decide when, and but this is simpler.

First, break down your main grid into two separate grids. You've got two panes, here, basically, so get their content in separate grids.

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

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

Give the Window a SizeChanged handler:

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

Update

This can also be done with a UniformGrid, if you prefer.

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

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

Code behind

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;
    }
}

Update 2

You can also switch grid column and row on the right pane:

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

Code behind:

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);
    }
}

I'd like to urge you to consider not using the Viewbox. Scaling fonts and controls to the window is unusual and not generally considered very usable. But it's your project.

If you do want to use the Viewbox, read up about its Stretch property, which governs how it scales its contents.

Have a look at ViewBox.StretchDirection as well.

  • Hi, thanks for your response. Your solution works great but I'm using a viewbox for the stretching of my second grid (person data). With the viewbox I don't have to specify fontsize, textbox width etc and all the elements get stretched according to the window size. It seems like the viewbox can't work that way inside a stackpanel [link](https://stackoverflow.com/questions/31118691/wpf-how-to-make-a-viewbox-aware-of-its-available-space-from-within-a-stackpanel) Any idea to work around that? – bego Jun 19 '17 at 07:38
  • @bego I don't know exactly what you're trying to achieve and I can't see your version of my code, so it's impossible to guess exactly how to fix it. If you wrap the `ViewBox` in a `Grid`, it will scale. I'd like to add, though, that scaling fonts and controls with the window size is a user interface idea that I last saw in old Windows API documentation from the early 90s. In practice, it turned out not to be a very good idea. – 15ee8f99-57ff-4f92-890c-b56153 Jun 19 '17 at 13:18
  • 1
    My problem is that I need to consider different screen sizes and resolutions. I thought, therefore, that the Viewbox would be a practical solution. The application I'm developing should be readable from a greater than usual distance because some screens are used as "public displays". Nevertheless they are not bigger than 23", so the labels & controls have to be pretty big. thanks for your efforts – bego Jun 20 '17 at 09:14
  • @bego Well if it works in your application, it works. Never say never! – 15ee8f99-57ff-4f92-890c-b56153 Jun 20 '17 at 15:36
  • I implemented your solution from the second update. For formatting reasons I had to try around with column and row span but now it works pretty well! thx again for the help! – bego Jun 21 '17 at 11:14
  • @15ee8f99-57ff-4f92-890c-b56153 Stupid question, i am new to C#. How do i use `private void Window_SizeChanged.....`? do you have a complete example of MainWindows.xaml.cs? I have placed it under `public MainWindow() {.....` and doesnt do anything. Thanks. – EPurpl3 Feb 26 '20 at 14:46
0

As a variation to the answer by Ed you can use a value converter to check that the width of the image is now less than the minimal size you want to apply to it. So you end up with something similar to a breakpoint concept in responsive web design.

This blog article explains this: https://www.iambacon.co.uk/blog/a-pattern-for-responsive-applications-in-wpf

This can be especially useful if you have many windows in the application that need this functionality so you can reuse the value converter across all of them.


In the simpler case that the image size is fixed and doesn't need to adjust with the window size you can use simply use a WrapPanel like this:

<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>
Mohammad
  • 1,930
  • 1
  • 21
  • 31
  • Hi & thanks! Unfortunately I also have to resize the image. I'm going to play around with the value converter and comment again later. – bego Jun 19 '17 at 08:31