How to evenly space children in a RelativePanel in Universal app - xaml

I have a UserControl that contains a RelativePanel. I'm using a RelativePanel because I need this UserControl to be displayed at the top of the screen when I'm on a narrow state (phone app) and displayed along the left edge of the screen when it is in a wider screen.
The children are buttons with images which I use as a toolbar for navigation. The problem is that I cannot find a way to evenly space the buttons in the RelativePanel. The buttons appear stuck to each other left-aligned.
I tried using a Grid, and it worked, but I was forced to create two user controls, one for the top menu and one for the left edge menu, because the VisualStateManager does not allow me to change ColumnDefinitions to RowDefinitions.
The advantage of a RelativePanel is that I can do it with one user control, avoiding code duplication.
Here is the XAML:
<UserControl
x:Class="Innobec.Mobile.Apps.CityScope.UserControls.TopHorizontalToolBar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Innobec.Mobile.Apps.CityScope.UserControls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="80"
d:DesignWidth="400">
<RelativePanel x:Name="MainPanel">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="WindowStates">
<VisualState x:Name="WideState">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="660"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="newsButton.(RelativePanel.Below)" Value="pinButton"/>
<Setter Target="weatherButton.(RelativePanel.Below)" Value="newsButton"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="NarrowState">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="1"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="newsButton.(RelativePanel.RightOf)" Value="pinButton"/>
<Setter Target="weatherButton.(RelativePanel.RightOf)" Value="newsButton"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Button x:Name="pinButton" Background="Red" VerticalAlignment="Center" Click="pinButton_Click" Style="{StaticResource TopHorizontalToolBarButtonStyle}">
<Image Source="/Assets/Top-Pin-Icon-60px.png" Stretch="None"/>
</Button>
<Button x:Name="newsButton" Background="Red" VerticalAlignment="Center" Click="newsButton_Click" Style="{StaticResource TopHorizontalToolBarButtonStyle}">
<Image Source="/Assets/Top-News-Icon-60px.png" Stretch="None"/>
</Button>
<Button x:Name="weatherButton" Background="Red" VerticalAlignment="Center" Click="weatherButton_Click" Style="{StaticResource TopHorizontalToolBarButtonStyle}">
<Image Source="/Assets/Top-Weather-Icon-60px.png" Stretch="None"/>
</Button>
</RelativePanel>
Is there a control that I can place inside the RelativePanel that will evenly space the children? Please note that I'm new to XAML so it may be possible, it's just that I haven't figured it out yet.

One option is to dynamically change the sizes of the RelativePanel's children in the code behind using the RelativePanel's SizeChanged event. You could even keep it fairly dynamic by doing something like this:
private void RelativePanel_SizeChanged(object sender, SizeChangedEventArgs e)
{
RelativePanel panel = sender as RelativePanel;
if(panel != null)
{
double childCount = panel.Children.Count;
for (int i = 0; i < panel.Children.Count; i++)
{
FrameworkElement child = panel.Children[i] as FrameworkElement;
if (child != null)
{
if(spacedHorizontally)
{
child.Height = panel.ActualHeight;
child.Width = panel.ActualWidth / childCount;
}
else
{
child.Height = panel.ActualHeight / childCount;
child.Width = panel.ActualWidth;
}
}
}
}
}
This would evenly space out the children either Horizontally or Vertically (using the spacedHorizontally bool in my example) and stretch the children in the other direction.

Related

How can I change visual of flyoutitem in xamarin shell on select?

I want to highlight current page by changing background color of a flyoutitem, but I also need to change text color inside a frame. My Template
<Grid ...>
<Frame CornerRadius="10"
Padding="0"
BackgroundColor="White"
Grid.Column="1">
<Label Text="{Binding Title}"
Margin="50,0,0,0"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center"
TextColor="Black"
Grid.Column="1"/>
</Frame>
</Grid>
I need to change Frame color to darker green and label text color to white whenever I navigate to a certain page. I've tried visual state manager, but it doesn't work even just with background-only. I found a solution to attach trigger on a Frame, but nothing happens
protected override void Invoke(Frame sender)
{
sender.BackgroundColor = Color.FromHex("#229904");
(sender.Content as Label).TextColor = Color.White;
}
Firstly, you need to add a custom style in your resources:
<Style x:Key="FloutItemStyle" TargetType="Grid">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Selected">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="xxx"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
Then consume it in your Shell:
<Shell.ItemTemplate>
<DataTemplate >
<Grid Style="{StaticResource FloutItemStyle}">
//...
</Grid>
</DataTemplate>
</Shell.ItemTemplate>

UWP CommandBar with multiple overflow menus like Word Mobile?

I have a basic C# UWP app running on the desktop. The app's layout is similar to Word Mobile, i.e. it has a main menu with a command bar below to apply various commands.
The default CommandBar has primary commands (displayed as icons) and secondary commands shown in the overflow menu.
The Word Mobile app uses a special command bar with "groups" of command buttons. Each group has their own overflow menu should the window size be too small to show all commands (see screenshots below).
Is there a way to get these "grouped" commands with their own overflow menu using standard XAML controls? If not, what would be a strategy to implement a custom control like this?
Example:
(1) Wide window: CommandBar shows all command buttons:
(2) Small window: two separate overflow menu buttons:
Sorry about the delay but I thought I would post a proof of concept answer.
For this example I have created 7 action AppBarButton in a CommandBar including the following
2 Buttons that are independent - AllAppsButton, CalculatorButton
2 Buttons that are part of the CameraGroup - CameraButton, AttachCameraButton
3 Buttons that are part of the FontGroup - BoldButton, FontButton, ItalicButton
The idea is that if the screen size is less than 501 pixels I use VisualStateManager and AdaptiveTriggers to show the Group Chevron Buttons instead of the action Buttons. The action Buttons are then shown by clicking the down chevron which opens up a relevant Popup control.
The Popups are hidden again using AdaptiveTriggers if the screen is increased to 501 pixels or above.
MainPage.xaml
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CameraGroupStates">
<VisualState x:Name="Default">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="501" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="CameraButton.Visibility" Value="Visible"/>
<Setter Target="AttachCameraButton.Visibility" Value="Visible" />
<Setter Target="CameraGroupButton.Visibility" Value="Collapsed" />
<Setter Target="CameraGroupPopup.IsOpen" Value="false" />
<Setter Target="FontButton.Visibility" Value="Visible"/>
<Setter Target="BoldButton.Visibility" Value="Visible" />
<Setter Target="ItalicButton.Visibility" Value="Visible" />
<Setter Target="FontGroupButton.Visibility" Value="Collapsed" />
<Setter Target="FontGroupPopup.IsOpen" Value="false" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Minimal">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="0" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="CameraButton.Visibility" Value="Collapsed"/>
<Setter Target="AttachCameraButton.Visibility" Value="Collapsed" />
<Setter Target="CameraGroupButton.Visibility" Value="Visible" />
<Setter Target="FontButton.Visibility" Value="Collapsed"/>
<Setter Target="BoldButton.Visibility" Value="Collapsed" />
<Setter Target="ItalicButton.Visibility" Value="Collapsed" />
<Setter Target="FontGroupButton.Visibility" Value="Visible" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<CommandBar x:Name="MainCommandBar" Height="50">
<AppBarButton x:Name="AllAppsButton" Icon="AllApps"></AppBarButton>
<AppBarButton x:Name="CameraButton" Icon="Camera"></AppBarButton>
<AppBarButton x:Name="AttachCameraButton" Icon="AttachCamera"></AppBarButton>
<!--This is the Group Chevron button for Camera-->
<AppBarButton x:Name="CameraGroupButton" Visibility="Collapsed" Content=""
Click="CameraGroupButton_Click">
<AppBarButton.ContentTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Content}"
FontFamily="Segoe MDL2 Assets" HorizontalAlignment="Center"></TextBlock>
</Grid>
</DataTemplate>
</AppBarButton.ContentTemplate>
</AppBarButton>
<AppBarButton x:Name="CaluclatorButton" Icon="Calculator"></AppBarButton>
<AppBarButton x:Name="BoldButton" Icon="Bold"></AppBarButton>
<AppBarButton x:Name="FontButton" Icon="Font"></AppBarButton>
<AppBarButton x:Name="ItalicButton" Icon="Italic"></AppBarButton>
<!--This is the Group Chevron button for Fonts-->
<AppBarButton x:Name="FontGroupButton" Visibility="Collapsed" Content=""
Click="FontGroupButton_Click">
<AppBarButton.ContentTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Content}"
FontFamily="Segoe MDL2 Assets" HorizontalAlignment="Center"></TextBlock>
</Grid>
</DataTemplate>
</AppBarButton.ContentTemplate>
</AppBarButton>
</CommandBar>
<Popup x:Name="CameraGroupPopup" HorizontalAlignment="Right" HorizontalOffset="-120"
VerticalOffset="50" IsOpen="False">
<StackPanel Orientation="Horizontal" Width="120" Height="60" Background="LightGray"
HorizontalAlignment="Center">
<AppBarButton x:Name="CameraGroupCameraButton" Icon="Camera" Width="50" Height="50"
Foreground="Black"/>
<AppBarButton x:Name="CameraGroupAttachCameraButton" Icon="AttachCamera"
Foreground="Black" Width="50" Height="50"></AppBarButton>
</StackPanel>
</Popup>
<Popup x:Name="FontGroupPopup" HorizontalAlignment="Right" HorizontalOffset="-170"
VerticalOffset="50" IsOpen="False">
<StackPanel Orientation="Horizontal" Width="170" Height="60" Background="LightGray"
HorizontalAlignment="Center">
<AppBarButton x:Name="FontGroupBoldButton" Icon="Bold" Width="50" Height="50"
Foreground="Black"/>
<AppBarButton x:Name="FontGroupFontButton" Icon="Font"
Foreground="Black" Width="50" Height="50"></AppBarButton>
<AppBarButton x:Name="FontGroupItalicButton" Icon="Italic"
Foreground="Black" Width="50" Height="50"></AppBarButton>
</StackPanel>
</Popup>
</Grid>
Code behind contains 2 fields and 2 events used to hide/show relevant Popups, change chevrons to up/down arrow.
public sealed partial class MainPage : Page
{
public bool CameraGroupIsOpen { get; set; } = false;
public bool FontGroupIsOpen { get; set; } = false;
public MainPage()
{
this.InitializeComponent();
}
private void CameraGroupButton_Click(object sender, RoutedEventArgs e)
{
if (CameraGroupIsOpen)
{
CameraGroupPopup.IsOpen = false;
CameraGroupButton.Content = "\uE019";
CameraGroupIsOpen = false;
}
else
{
CameraGroupPopup.IsOpen = true;
CameraGroupButton.Content = "\uE018";
CameraGroupIsOpen = true;
}
}
private void FontGroupButton_Click(object sender, RoutedEventArgs e)
{
if (FontGroupIsOpen)
{
FontGroupPopup.IsOpen = false;
FontGroupButton.Content = "\uE019";
FontGroupIsOpen = false;
}
else
{
FontGroupPopup.IsOpen = true;
FontGroupButton.Content = "\uE018";
FontGroupIsOpen = true;
}
}
}
Quite a bit of code that could no doubt be vastly improved as a CustomControl but wanted to show the idea. Hope it helps
It's probably not super complicated, but it can be a lot of work nevertheless. It probably derives from the the ribbon where there are multiple groups with item collapse and drop priorities (I think) and items get dropped in priority order.
You would need to have a container panel that would read priorities from the groups included in it and in MeasureOverride - it would drive collapsing items in the groups when space pressure happens.
The ToolStrip control in my toolkit has code to move items between the toolstrip and the overflow dropdown (check sample here) and could be a good first step of implementing support for ribbon-like collapsing.
IIRC the official sample for CommandBar also has logic implemented to move items between PrimaryCommands and SecondaryCommands which you could look into.

Select a different grid based on a converter's value for a UWP app

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.

Windows 10 UAP : Binding a Command bar

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)

Windows Universal responsive design reposition

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