So, I've just started working again with Windows apps and there are a few things that I can't get working as I want (probably because I coulnd't find any sample and Channel9 videos didn't cover my case).
Starting from this article, I decided that the "reposition" technique is the one that fits my app when moving from a big screen to a smaller one.
What I did is using a StackPanel and changing its orientation using two AdaptiveTriggers (one for 0 width and another one for 720, based on the table here).
This kinda works but there are some issues that I'll illustrate with some ugly paint-edited screenshots.
This is what happens when I'm in the BigScreen situation, where there's enough space to have both A and B on the same row. The problem here is that B should take the full remaining width, covering all the blue part.
The second issue is related to resizing. When there's not enough space, the green part gets cut instead of being resized (you can see that the right border disappeared). This didn't happen before using the StackPanel to make the layout responsive.
Finally, when we are in the SmallScreen situation, orientation changes to be vertical and we have the same problem as the first one: green part's height doesn't fill the screen.
Here's the XAML used for the page:
<Page
x:Class="Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:WifiAnalyzerFinal.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mvvm="using:Mvvm"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="SmallScreen">
<VisualState>
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="0"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="StackPanel.Orientation"
Value="Vertical"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="BigScreen">
<VisualState>
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="720"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="StackPanel.Orientation"
Value="Horizontal"/>
<Setter Target="Rect.Width"
Value="200"/>
<Setter Target="Rect.Height"
Value="Auto"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<StackPanel Orientation="Vertical"
Background="Blue"
x:Name="StackPanel">
<Rectangle Fill="Red"
Height="50"
x:Name="Rect"
Width="Auto"/>
<ListView ItemsSource="{Binding Stuff}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalAlignment="Stretch"
Background="Green"
Width="Auto"
BorderBrush="DarkGreen"
BorderThickness="5"
Padding="5">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Margin" Value="0,0,0,5"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
</StackPanel>
</Grid>
</Page>
Please note that without the StackPanel the green part fits the page as it should, covering all the available area. Unfortunately I couldn't came up with a better solution because there's no sample telling us how this technique should be implemented. I also tried using the new RelativePanel but it seems that the AdaptiveTrigger's Setter doesn't work with attached properties like RelativePanel.RightOf.
Is there someone who's been successful is applying this technique without having to use code-behind?
EDIT:
I got this working using a Grid with 2 rows and 2 columns, with the AdaptiveTrigger moving all the content from row to column and viceversa.
It is possible to change RelativePanel attached property values through setters. The syntax goes like this:
<Setter Target="SomeXAMLObject.(RelativePanel.RightOf)" Value="SomeOtherXAMLObject" />
Why don't you try to use grid (instead of StackPanel) and define rows using proportional dimensions like:
`<Grid>
<Grid.RowDefinitions>
<RowDefinition width="2*"/>
<RowDefinition width="3*"/>
<RowDefinition width="1*"/>
</Grid.RowDefinitions>
</Grid>`
Related
I am using a VisualStateManager in the XAML to specify a change in the width/height of a grid column/row by using the ColumnDefinition and RowDefinition x:Name properties. I read here that this is possible... but Visual Studio says "Failed to find element named _____" with a warning, and the state change for the grid row does not work (the setters for changing row/column assignments trigger correctly, so I know the VisualStateManager is working overall).
The intended effect is to have the content vertically take up exactly the screen (with no vertical scrolling) in the wide state, with each block being in a different column. Then in the narrow state, the content will reflow to instead be all in the same column but different rows (thus vertically stacked), and thus vertical scrolling will be necessary (the content will also change from having a fixed width in the wide state, to stretching to take up the window width in the narrow state).
My code looks something like this:
<Page>
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="VisualStateGroup">
<VisualState x:Name="Narrow">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="0"></AdaptiveTrigger>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="ContentBlockA.(Grid.Column)" Value="0"></Setter>
<Setter Target="ContentBlockB.(Grid.Column)" Value="0"></Setter>
<Setter Target="ContentBlockC.(Grid.Column)" Value="0"></Setter>
<Setter Target="ContentBlockA.(Grid.Row)" Value="2"></Setter>
<Setter Target="ContentBlockB.(Grid.Row)" Value="1"></Setter>
<Setter Target="ContentBlockC.(Grid.Row)" Value="0"></Setter>
<Setter Target="ContentRowZero.Height" Value="Auto"></Setter>
<Setter Target="ContentColumnZero.Width" Value="*"></Setter>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Wide">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="900"></AdaptiveTrigger>
</VisualState.StateTriggers>
<VisualState.Setters>
<!-- Empty setters defaults to the layout specified in XAML below. -->
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ScrollViewer>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="ContentColumnZero"
Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition x:Name="ContentRowZero"
Height="*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<Grid Name="ContentBlockA"
Grid.Column="0">
<!-- Content here -->
</Grid>
<Grid Name="ContentBlockB"
Grid.Column="1">
<!-- Content here -->
</Grid>
<Grid Name="ContentBlockC"
Grid.Column="2">
<!-- Content here -->
</Grid>
</Grid>
</ScrollViewer>
</Grid>
</Page>
I'm working on a UWP app. I'm already using adaptive triggers to adapt my xaml depending on the width of the window, and it works.. only in a Page.
Now I want to do the same for the xaml of a User Control, and it's not working .. Yet I put the VisualStateManager to root grid of the user control.
Is there a difference ?
Here's the code of my user control :
<UserControl
x:Class=.....>
<UserControl.Resources>
<ResourceDictionary>
<vm:ViewModelLocator x:Key="Locator"/>
<converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</ResourceDictionary>
</UserControl.Resources>
<UserControl.DataContext>
<Binding Path="MyUserControlVM" Source="{StaticResource Locator}"/>
</UserControl.DataContext>
<Grid x:Name="LayoutRoot" Background="white">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="Narrow">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="0" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="LastName.Foreground" Value="Red" />
<Setter Target="LastName.Fontsize" Value="10" />
<Setter Target="FirstName.Foreground" Value="Red"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Normal">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="600" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="LastName.Foreground" Value="Red" />
<Setter Target="LastName.Fontsize" Value="25" />
<Setter Target="FirstName.Foreground" Value="Red"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Wide">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="1000"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="LastName.Foreground" Value="Red" />
<Setter Target="FirstName.Foreground" Value="Red"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Listbox>
...
</ListBox>
<TextBlock x:Name="lblNoData" Grid.ColumnSpan="2" Text="No Data" Visibility="{Binding NoDataVisible, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<ProgressRing x:Name="prLoading" Width="60" Height="60" Foreground="Blue" IsActive="{Binding InitializationNotifier.IsNotCompleted}" />
</Grid>
("LastName" and "FirstName" are textblocks in the DataTemplate of my listbox. I'm just trying to put my text in red in order to see when the triggers work)
And I call the user control in a simple page like this :
<Grid x:Name="LayoutRoot" Background="{StaticResource white}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Margin="0,0,0,10">
...
</StackPanel>
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="10,0">
<UC:MyUserControl/>
</Grid>
</Grid>
I don't understand why this code won't work. Thank you in advance for your help !
("LastName" and "FirstName" are textblocks in the DataTemplate of my listbox. I'm just trying to put my text in red in order to see when the triggers work)
You can refer to my another answer here, as I said in that answer, when the controls are placed in the DataTemplate, they becomes the visual structure of your data objects. I think there is no clean way to do this work only in the xaml code, Data Binding here is your friend.
From your code I can see that you want to change the Foreground (but all to Red?) and the FontSize of the TextBlocks inside of the DataTemplate depending on the window's size. So you can bind these two proprieties to the window's size, or you can use ItemsControl.ItemTemplateSelector to select different template when the window's size is changed.
In a UWP app, I have a property that returns 3 values. I want to show a different grid based on this value using a converter. What is the best way to a achieve this? The direction I think I am going to head towards is to create 3 different grid templates, and then set the style to one of these 3 templates based on what the converter returns. Does anyone know if this will work? My grid doesn have to be a grid, it can be a contentcontrol or something like that. I basically want to show a different section of UI based on a property
Thanks
I would use the WindowsStateTriggers NuGet package.
Once installed you can reference at the top of your xaml
xmlns:triggers="using:WindowsStateTriggers"
and say you had a property in your Backend class called IsBusy
public bool IsBusy { get; set; } = false;
and for example you had 2 simple Grids in your xaml.
<StackPanel x:Name="root">
<Grid x:Name="RedGrid" Background="Red" Width="200" Height="100" />
<Grid x:Name="GreenGrid" Background="Green" Width="200" Height="100" Visibility="Collapsed"/>
</StackPanel>
You could setup a Visual State Group to show the Grids based on the IsBusy property. We want the Green Grid to be visible and the Red Grid to be collapsed when IsBusy = true
<StackPanel x:Name="root">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="IsBusy">
<VisualState.StateTriggers>
<triggers:EqualsStateTrigger EqualTo="True" Value="{Binding IsBusy, ElementName=root}" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="RedGrid.Visibilty" Value="Collapsed"/>
<Setter Target="GreenGrid.Visibility" Value="Visible" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid x:Name="RedGrid" Background="Red" Width="200" Height="100" />
<Grid x:Name="GreenGrid" Background="Green" Width="200" Height="100" />
</StackPanel>
NB The {Binding IsBusy, ElementName=root} depends on your DataContext and location of IsBusy property. Here its just in the code behind for the page.
Hope that gives you an idea.
I'm making an UAP Windows 10 App. Like app News from Microsoft, i'm trying to put the CommandBar in top on the page in desktop view, and in bottom of the page in mobile view.
How could I do this ? I think I have an independant <CommandBar xxx> with empty <Page.TopAppBar> and <Page.BottomAppBar>. And depending of the size of the view, I attach the CommandBar to TopAppBar or BottomAppBar... ?
Having ideas ? Thanks you.
You can use the VisualState class to adjust alignment of things depending on the size of the screen, using the StateTriggers property. Here's some documentation about the class including some sample code here https://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.visualstate.statetriggers.aspx
This sample code demonstrates the use: https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/XamlStateTriggers
Simple implementation:
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState>
<VisualState.StateTriggers>
<!--VisualState to be triggered when window width is >=720 effective pixels.-->
<AdaptiveTrigger MinWindowWidth="720" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="myPanel.VerticalAlignment" Value="Bottom" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<StackPanel x:Name="myPanel" VerticalAlignment="Top">
<TextBlock Text="This is a block of text for an example. "
Style="{ThemeResource BodyTextBlockStyle}"/>
</StackPanel>
(that's this example but with the values changed for your situation. https://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.visualstate.statetriggers.aspx)
I added a ProgressBar to my application and I wanted it to have a transparent background, so I did it like this:
<ProgressBar IsIndeterminate="True" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<ProgressBar.Background>
<SolidColorBrush Color="Black" Opacity="0.5" />
</ProgressBar.Background>
</ProgressBar>
And in the Preview Window everything looks fine, however, when I run my app, the Background is simply not there. The solution I found to this is to put the ProgressBar in a Grid and set the Background property in the Grid, but since the Preview shows it right, and the property is there, shouldn't it work?
UPDATE:
Based on #Chris W. suggestion, I tried to override the default style of the ProgressBar element, like so:
<ProgressBar IsIndeterminate="True" Background="#FF000000" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="50">
<ProgressBar.Style>
<Style TargetType="ProgressBar">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ProgressBar">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Indeterminate">
<Storyboard RepeatBehavior="Forever">
<DoubleAnimation Storyboard.TargetName="DeterminateRoot"
Storyboard.TargetProperty="Opacity"
To="0.5"
Duration="0" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ProgressBar.Style>
</ProgressBar>
But still, no juice.
Need to get rid of two (2) StoryBoard Animations
Document Outline > Right Click Progress Bar > Edit Template -> Edit A Copy
<!--
<FadeOutThemeAnimation Storyboard.TargetName="DeterminateRoot"/>
<DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="DeterminateRoot"/>
-->
And as #ricochete suggested if using Opacity = 1 change up the Z-Order of DeterminateRoot to be on top of the EllipseGrid
<Border x:Name="DeterminateRoot" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" MinHeight="{TemplateBinding MinHeight}">
<Rectangle x:Name="ProgressBarIndicator" Fill="{TemplateBinding Foreground}" HorizontalAlignment="Left" Margin="{TemplateBinding Padding}"/>
</Border>
<Grid x:Name="EllipseGrid" Opacity="0">
<!-- ... more XAML Style -->
<Grid Background="#FFFF0000">
<ProgressBar IsIndeterminate="True" Style="{StaticResource ProgressBarStyle1}" Height="50" >
<ProgressBar.Background>
<SolidColorBrush Color="Black" Opacity="0.5"/>
</ProgressBar.Background>
</ProgressBar>
</Grid>
If you go look at the default template you'll see at the bottom of the template the Background only has a TemplateBinding in one spot for x:Name="DeterminateRoot" so that's the only place you'd see your color set from the Background property.
Then if you climb up through the Storyboard for the Indeterminate State you'll find;
<DoubleAnimation Storyboard.TargetName="DeterminateRoot"
Storyboard.TargetProperty="Opacity"
To="0"
Duration="0" />
...and you've got your ProgressBar set to IsIndeterminate="True" so you're setting the one place that accepts the Background property to a zero opacity explicitly.
So you could go pull that animation out of the Storyboard for that state, or put in your own new object to set your thing, or just do the workaround you mentioned by just throwing it in a Border or a Grid or something and doing it that way amongst other possibilities.
You might also try (once you've fixed your opacity setting issue from the storyboard) just flipping your SolidColorBrush with Opacity into just pure hex with the Alpha Channel set as 50% opacity equivalent. Making it just;
<ProgressBar Background="#80000000" IsIndeterminate="True" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"/>
Anyhow, hope this helps, Cheers!