WPF position elements depending on window size - c#

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!

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.

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>

Related

Implement responsive player with ScrollViewer

I need to realize how to do responsive MediaPlayer like this
And what i have
ScrollViewer prevents the second row from taking up space
This is my XAML code:
<ScrollViewer>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<MediaPlayerElement AreTransportControlsEnabled="True"
Name="player"
PosterSource="{Binding SelectedMedia.Poster, UpdateSourceTrigger=PropertyChanged}"
Source="{Binding SelectedMedia.Video_source}"
Stretch="Uniform"
HorizontalAlignment="Center"
AutoPlay="{Binding IsStreamPlay}"
RelativePanel.Above="AboutGrid">
<MediaPlayerElement.TransportControls>
<controls:CustomMediaTransportControls IsCompact="True"
IsZoomButtonVisible="False"
IsPlaybackRateButtonVisible="False"
IsVolumeEnabled="True"
IsVolumeButtonVisible="True"
IsSeekBarVisible="False"
IsPlaybackRateEnabled="False"
QualityCommand="{Binding SetQualityCommand}"
Qualities="{Binding Qualities, Mode=TwoWay}">
</controls:CustomMediaTransportControls>
</MediaPlayerElement.TransportControls>
</MediaPlayerElement>
<Grid x:Name="AboutGrid"
Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Image Source="{Binding SelectedMedia.Atr_url}"
Width="85"
Height="113"
Grid.Row="0"
Grid.RowSpan="3"
VerticalAlignment="Top"
Margin="4"
HorizontalAlignment="Left"/>
<Grid Grid.Column="1"
Grid.Row="0"
Grid.RowSpan="4"
Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="120"/>
</Grid.ColumnDefinitions>
<StackPanel HorizontalAlignment="Left">
<TextBlock FontWeight="SemiBold"
TextWrapping="Wrap"
FontSize="17"
Text="{Binding SelectedMedia.Title}" />
<TextBlock FontWeight="SemiLight"
TextWrapping="Wrap"
Text="{Binding SelectedMedia.Description}"/>
<TextBlock FontWeight="SemiLight">
<Run FontWeight="Normal"
Text="{Binding SelectedMedia.Game_name, UpdateSourceTrigger=PropertyChanged}"/>
</TextBlock>
</Grid>
</Grid>
</Grid>
</ScrollViewer >
I think the problem is that ScrollViever does not properly resize its children, what can I do about it?
Grid's proportional self-adaptation has preconditions and needs to be calculated based on the width of the current parent container.
But for the ScrollViewer, you can think of it as an "infinite" inner space, so that the Grid's adaptation will not take effect.
If you want to achieve the effect like Twitch, you can choose to limit the height of the MediaPlayerElement, like this:
xaml
<ScrollViewer>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Background="Black" x:Name="MediaContainer">
<MediaPlayerElement
...
HorizontalAlignment="Center"/>
</Grid>
...
</Grid>
</ScrollViewer>
xaml.cs
public VideoPage()
{
this.InitializeComponent();
this.SizeChanged += Page_SizeChanged;
}
private void Page_SizeChanged(object sender, SizeChangedEventArgs e)
{
double height = e.NewSize.Height;
MediaContainer.Height = height * 0.8;
}
Best regards.

Dynamically resizing TextBox with scroll bar

Is it possible to have a scrolling (multiline) TextBox without explicitly setting the height? Here is my example code snippet:
<Grid Grid.Row="6"
Grid.Column="2"
Grid.ColumnSpan="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Label Grid.Row="0">Heading</Label>
<TextBox Grid.Row="1"
SpellCheck.IsEnabled="True"
TextWrapping="Wrap"
AcceptsReturn="True"
VerticalScrollBarVisibility="Auto"></TextBox>
</Grid>
The problem here is that the TextBox will resize itself depending on how many lines the user enters without resorting to instead staying the same size and scrolling.
Simply add VerticalAlignment="Top"
<Grid Grid.Row="6"
Grid.Column="2"
Grid.ColumnSpan="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Label Grid.Row="0">Heading</Label>
<TextBox
VerticalAlignment="Top"
Grid.Row="1"
SpellCheck.IsEnabled="True"
TextWrapping="Wrap"
AcceptsReturn="True"
VerticalScrollBarVisibility="Auto"></TextBox>
</Grid>
This will get you started. As you have not put your requirements clearly, it's difficult to put a proper solution. If you put your more clear requirements, I can make a better version for you.
<Window ...>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="476*"/>
<ColumnDefinition Width="45*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="8*"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox x:Name="textBox" HorizontalAlignment="Left" Width="{Binding Value, ElementName=HorizontalSB}" Height="{Binding Value, ElementName=VerticalSB}" Margin="30,37,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" />
<ScrollBar x:Name="VerticalSB" Grid.Column="1" HorizontalAlignment="Center" Width="Auto" SmallChange="1" Minimum="50" Value="25" MaxHeight="250" Maximum="1000"/>
<ScrollBar x:Name="HorizontalSB" Grid.Row="1" VerticalAlignment="Top" Width="Auto" Orientation="Horizontal" VerticalContentAlignment="Center" SmallChange="10" Maximum="1000" Minimum="20" LargeChange="10" Value="75"/>
</Grid>
</Window>

ScrollViewer is not working inside groupbox?

I want to include ScrollViewer in my Groupbox, but it is not working. My code is:
<GroupBox
Margin="10,10,0,0"
Grid.Row="0"
Grid.ColumnSpan="3"
Height="150"
>
<ScrollViewer>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="140"/>
<ColumnDefinition Width="120"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<Label
Margin="0,6,0,0"
Content="SSID"
Grid.Row="0"
>
</Label>
<TextBox
Margin="0,6,0,6"
Grid.Column="1">
</TextBox>
<Label
Margin="0,6,0,0"
Content="(1024)"
Grid.Column="2"
>
</Label>
<Label
Margin="0,6,0,0"
Content="Authentication Mode"
Grid.Column="0"
Grid.Row="1"
>
</Label>
<ComboBox
Margin="0,6,0,6"
Grid.Row="1"
Grid.Column="1"
ItemsSource="{Binding ACAvailableSecurityTypes}"
SelectedItem="{Binding ACSelectedSecurityType}"
/>
<Label
Margin="0,6,0,0"
Grid.Row="2"
Grid.Column="0"
Content="VLAN"
/>
<TextBox
Margin="0,6,0,6"
Grid.Row="2"
Grid.Column="1"
/>
<Label Grid.Row="2"
Grid.Column="2"
Content="(1-4094)"/>
<Button
Grid.Row="3"
Grid.Column="2"
Content="Add SSID"
HorizontalAlignment="Left"
Width="70"
Style="{StaticResource AppButtons}"/>
</Grid>
</ScrollViewer>
</GroupBox>
In Order to See your Scroll bar, your scrollviewer should have lesser height than your groupbox, do like this, you can see the scrollbar, Set the height and VerticalScrollBarVisibility
<ScrollViewer Height="100" VerticalScrollBarVisibility="Auto">
<GroupBox
Margin="10,10,0,0"
Grid.Row="0"
Grid.ColumnSpan="3"
Height="150"
>
......
</GroupBox>
</ScrollViewer>
Place the ScrollViewer outside the GroupBox, not inside:
<ScrollViewer>
<GroupBox Margin="10,10,0,0"
Grid.Row="0"
Grid.ColumnSpan="3"
Height="150" >
...
...
</GroupBox>
</ScrollViewer>
Placing the GroupBox inside the ScrollViewer cause GroupBox header to disappear when scrolling.
I solved the problem, keeping the ScollViewer inside the GroupBox by setting the Height of the ScrollViewer to match GroupBox Height:
<GroupBox Header="Testing 123">
<ScrollViewer Height="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=GroupBox}, Path=ActualHeight}">
<Image .../>
</ScrollViewer>
</GroupBox>

Windows Store App - XAML - Border filling page rather then wrapping Grid

I'm trying to put a <Border/> around a <Grid/> in a page, however the border appears to be bordering the page rather than the grid.
This is only XAML in my page element.
<Border Background="Black">
<Grid Background="{ThemeResource ControlBackgroundBrush}" x:Name="LoginCredentials" Margin="5" HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock TextWrapping="Wrap" Text="Username:" Style="{StaticResource SubheaderTextBlockStyle}" VerticalAlignment="Center" Grid.Row="0" HorizontalAlignment="Right" Margin="5"/>
<TextBox x:Name="UserName" TextWrapping="Wrap" HorizontalAlignment="Left" VerticalAlignment="Center" Text="" Grid.Row="0" Grid.Column="1" TextChanged="UserChange" Margin="5"/>
<Button x:Name="LoginButton" Content="Login" Click="LoginButton_Click" Grid.Row="2" HorizontalAlignment="Right" Margin="5" TabIndex="0"/>
<Button x:Name="CancelButton" Content="Cancel" Click="CancelButton_Click" Grid.Row="2" Grid.Column="1" />
</Grid>
</Border>
As a test I created a resource to fill the background of the <Grid/> with a colour and also filled the background of the <Border/> with a different colour. The <Grid/> ends up as a box in the center of the screen as intended, but the border <Border/> fills the entire screen. Can anyone tell me why this happens and how to get the <Border/> to fit around the <Grid/> as I want?
Got it!
<Border Background="Black" VerticalAlignment="Center" HorizontalAlignment="Center">
....
</Border>
As soon as i posted the question, what i was missing became clear!

Column marging on a WPF Grid

I created a grid. On this grid, i have two colums with two TextBlock
I would like to insert a space between my columns, in order to having space between my textBlocks.
How doing this ?
Here is my code :
<ListBox x:Name="ListBoxTiers" HorizontalContentAlignment="Stretch" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch" VerticalAlignment="Top">
<Grid Margin="10" VerticalAlignment="Top" HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" x:Name="TxtBox_CodeTiers" TextWrapping="Wrap" Text="{Binding m_strCode}" HorizontalAlignment="Stretch" VerticalAlignment="Top" />
<TextBlock Grid.Row="0" Grid.Column="1" x:Name="TxtBox_NomTiers" TextWrapping="Wrap" Text="{Binding m_strNom}" HorizontalAlignment="Stretch" VerticalAlignment="Top" />
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Thanks a lot :)
Instead of playing with the columnns, set a margin around the textbox.
<TextBox Margin="10">
You can set each side independently or set left/right and up/down:
<TextBox Margin="10, 3, 7, 0">
<TextBox Margin="10, 5">
Or wrap your TextBoxes inside another panel and set the margin there:
<Grid Margin="10">
<TextBox />
<TextBox />
</Grid>

Categories

Resources