Is there a way to create master / template pages in XAML (for UWP applications)?
The problem I'm trying to solve:
I have an application with a lot of similar sites, where only the content changes slightly but not the Buttons and the Layout. Example:
<Page
DataContext="{Binding WebpageViewModel, Source={StaticResource Locator}}">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Edit Webpage" Style="{StaticResource BigTexBlock}" />
<ScrollViewer Style="{StaticResource ContentScrollViewer}" Grid.Row="1" VerticalScrollMode="Enabled">
<StackPanel Margin="10,0">
<webpage:EditWebpage DataContext="{Binding }" />
</StackPanel>
</ScrollViewer>
</Grid>
</Grid>
<Page.BottomAppBar>
<CommandBar>
<CommandBar.PrimaryCommands>
<!-- more buttons -->
<AppBarButton IsCompact="True" Command="{Binding SaveEntryCommand}" Icon="Save" Label="Save" />
</CommandBar.PrimaryCommands>
</CommandBar>
</Page.BottomAppBar>
</Page>
Only three parts of this template will change; the ViewModel in the DataContext, the Text of the TextBlock, and the UserControl which contains the editable fields.
As this is an application with a lot of CRUD happening with simple Entities the amount of code to be repeated over and over again is a lot if I keep "solving" the problem like this. In the separated business logic I could avoid this problem with inheritance, but I'm struggling to find a elegant solution in XAML.
Is there a way to refactor this so I may have a "Template Page"?
I like how for example twig has solved this problem: http://twig.sensiolabs.org/. You define a master/template page and override parts of it in the children templates.
Important to me is that
I don't break the MVVM pattern.
I don't want to hide/show UserControls in one "Main" XAML as the amount of different entities may become quite large
I want navigation happening between the pages that the user sees the expected animations, and it does not break the separated view code I already have
There isn't a master page or template mechanism that other technologies, like MVC, have. But you can use frames and navigation to do what you're looking for.
You could keep the page defined the way you currently have it. All of the fixed elements on the page are in the layout. Now instead of using a UserControl for your specific edit UI, replace that with a frame.
<StackPanel Margin="10,0">
<Frame Name="EditFrame" DataContext="{Binding }" />
</StackPanel>
Now when you navigate to the Main Edit page, also pass the type for the view you want in the frame. Then on your OnNavigatedTo override for the main page, you can navigate the frame to the view type as the parameter.
You can also use the EditFrame to page through multiple editing pages, like if you had a wizard UI with Next and Previous buttons, without leaving the main page.
You can either do this in your OnNavigatedTo method or modify your NavigationService to be able to handle this behavior.
I have solved this problem with the approach suggested by https://stackoverflow.com/a/43170663
my NavigationService
//get the current frame
var frame = (Frame)Window.Current.Content;
//navigate to the generic AddEntry page
frame.Navigate(typeof(AddEntryPage), new NavigationParameter() { /* set props needed */ });
my xaml (my "master page") looks now like this (using a Frame now):
<Page
x:Class="Famoser.Bookmarked.Presentation.Universal.Pages.Entry.Webpage.AddEntryPage"
mc:Ignorable="d"
d:DataContext="{Binding WebpageViewModel, Source={StaticResource Locator}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock x:Name="Title" Grid.Row="0" Text="Add "/>
<ScrollViewer Grid.Row="1">
<StackPanel Margin="10,0">
<Frame x:Name="EntryFrame" />
</StackPanel>
</ScrollViewer>
</Grid>
<Page.BottomAppBar>
<CommandBar>
<CommandBar.PrimaryCommands>
<!-- more app buttons -->
<AppBarButton IsCompact="True" Command="{Binding SaveEntryCommand}" Icon="Save" Label="Save" />
</CommandBar.PrimaryCommands>
</CommandBar>
</Page.BottomAppBar>
</Page>
and in the code behind in the navigation event I set the properties passed by my NavigationService
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (e.Parameter is NavigationParameter pm)
{
DataContext = SimpleIoc.Default.GetInstance(pm.ViewModelType);
Title.Text = "Add " + pm.Name;
EntryFrame.Navigate(pm.EditFrameType);
}
}
the full project in on github: https://github.com/famoser/Bookmarked
Related
I am an experienced uwp developer but a beginner for uwp xbox platform. I am trying to set the XY Navigation for my app and trying to test it with keyboard (as I don't own a xbox myself).
I am using a Pivot view and I can easily navigate between the pivot items with right and left arrow keys, which makes sense. but when my settings page is selected with pivot option (settings pivot header is focused and settings pivot item is in view) then I try to shift my focus vertically downwards to the first control in the settings page (radio buttons) but I am not able to do it the focus remains on settings header and doesn't shift downward on the page.
So how can I shift the focus downwards from a pivot header to the 1st control within the page on pressing down, and vice versa i.e : when 1st control is focused I should move up to go back to the header of the pivot of that page, because I think that is the traditional navigation with pivot control on uwp xbox right?
Secondly the docs and the xbox app dev videos I watched recommended to set the focus on an element which makes sense, when the app loads, should that be done with this.Focus() method or is there a more efficient way to do it with xaml?
Code:
Pivot.xaml
<Grid x:Name="MainGrid">
<Pivot x:Uid="PivotPage" x:Name="MainPivot" >
<PivotItem x:Uid="PivotItem_OnNow">
<Frame>
<views:OnNowPage/>
</Frame>
</PivotItem>
<PivotItem x:Uid="PivotItem_Guide">
<Frame>
<views:GuidePage/>
</Frame>
</PivotItem>
<PivotItem x:Uid="PivotItem_Settings">
<Frame>
<views:SettingsPage/>
</Frame>
</PivotItem>
</Pivot>
</Grid>
Settings.xaml
<Grid>
<Grid Margin="{StaticResource MediumLeftRightMargin}">
<Grid.RowDefinitions>
<RowDefinition Height="48"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock
Grid.Row="0"
x:Uid="Settings_Title"
x:Name="TitlePage"
Style="{StaticResource PageTitleStyle}" />
<StackPanel Grid.Row="1">
<TextBlock
x:Uid="Settings_Personalization"
Style="{StaticResource SubtitleTextBlockStyle}" />
<StackPanel Margin="{StaticResource SettingsSubheaderMargin}">
<TextBlock
x:Uid="Settings_Theme"
Style="{StaticResource BodyTextStyle}" />
<StackPanel Margin="{StaticResource EightTopMargin}">
<RadioButton
x:Uid="Settings_Theme_Light"
GroupName="AppTheme"
IsChecked="{x:Bind ViewModel.ElementTheme, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter=Light, Mode=OneWay}"
Command="{x:Bind ViewModel.SwitchThemeCommand}">
<RadioButton.CommandParameter>
<xaml:ElementTheme>Light</xaml:ElementTheme>
</RadioButton.CommandParameter>
</RadioButton>
<RadioButton
x:Uid="Settings_Theme_Dark"
GroupName="AppTheme"
IsChecked="{x:Bind ViewModel.ElementTheme, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter=Dark, Mode=OneWay}"
Command="{x:Bind ViewModel.SwitchThemeCommand}">
<RadioButton.CommandParameter>
<xaml:ElementTheme>Dark</xaml:ElementTheme>
</RadioButton.CommandParameter>
</RadioButton>
<RadioButton
x:Uid="Settings_Theme_Default"
GroupName="AppTheme"
IsChecked="{x:Bind ViewModel.ElementTheme, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter=Default, Mode=OneWay}"
Command="{x:Bind ViewModel.SwitchThemeCommand}">
<RadioButton.CommandParameter>
<xaml:ElementTheme>Default</xaml:ElementTheme>
</RadioButton.CommandParameter>
</RadioButton>
</StackPanel>
</StackPanel>
<TextBlock
x:Uid="Settings_About"
Style="{StaticResource SubtitleTextBlockStyle}"/>
<StackPanel Margin="{StaticResource EightTopMargin}">
<TextBlock
Text="{x:Bind ViewModel.VersionDescription, Mode=OneWay}" />
<TextBlock
x:Uid="Settings_AboutDescription"
Margin="{StaticResource EightTopMargin}" />
<HyperlinkButton
x:Uid="Settings_PrivacyTermsLink"
Margin="{StaticResource EightTopMargin}" />
</StackPanel>
</StackPanel>
</Grid>
</Grid>
The MSDN has listed several scenarios that XY navigation might not work the way you expect:
The IsTabStop or Visibility property is set wrong.
The control getting focus is actually bigger than you think—XY navigation looks at the total size of the control (ActualWidth and ActualHeight), not just the portion of the control that renders something interesting.
One focusable control is on top of another—XY navigation doesn't support controls that are overlapped.
If XY navigation is still not working the way you expect after fixing these issues, you can manually point to the element that you want to get focus using the method described in Overriding the default navigation.
Please first check these scenarios, after that, if you still could not solve this issue. Please provide a Minimal, Complete, and Verifiable example. I will help you diagnose it on my side.
I have a couple of questions about hierarchical template formatting for a TreeView. This image will illustrate:
I want to remove the extra space between the top of the type and the border.
I want to center the icon between the two lines of type
I want to add a thousands comma. I've tried this but there is a problem with adding a comma with a bound data.
Here is the XAML code for the third level:
<HierarchicalDataTemplate
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
ItemsSource="{Binding XPath=Unit}"
>
<Grid Height="42" Width="auto" >
<Grid Height="41" HorizontalAlignment="Left" Margin="0,0,0,0" Name="grid1" VerticalAlignment="Top" Width="auto">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Image Source= "{Binding XPath=UnitIcon}" Grid.Column="1" Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Left" Stretch="None" OpacityMask="White"></Image>
<Label Content="{Binding XPath=UnitName}" Height="54" HorizontalAlignment="Left" Name="label4" VerticalAlignment="Top" FontFamily="Smythe" FontSize="18" Margin="0,0,0,0" Grid.RowSpan="3" Grid.Column="2" Grid.ColumnSpan="3"/>
<Label Content="Strength:" FontFamily="Amaltea WF" FontSize="12" HorizontalAlignment="Left" Margin="0,0,0,0" VerticalAlignment="Center" Grid.Column="2" Grid.Row="2"/>
<TextBlock Text="{Binding XPath=UnitStrength, ConverterParameter=N0}" Margin="0,0,0,0" FontFamily="BauderieScriptSSK Bold" FontSize="18" HorizontalAlignment="Left" VerticalAlignment="Center" Grid.Column="3" Grid.Row="2"/>
</Grid>
<Line X1='0'
X2='200'
Y1='0'
Y2='0'
Stroke="Gray"
StrokeThickness='1' />
</Grid>
</HierarchicalDataTemplate>
Thanks in advance for the help!
Take the fixed height off the UnitName label. You've got grid cells, you don't want fixed heights. Part of that gap may be the line height from your font. Temporarily set Background="LightSkyBlue" on the label to see how much space the label itself is actually taking up.
Looks like VerticalAlignment="Center" on the image isn't having the desired effect because you've put conflicting fixed heights on everything. Your grid1 is fixed at 41 units high, but the unit name within it is 54 units high. The layout engine is doing its best to comply with the contradictory orders you're giving it.
Delete every fixed height in your XAML. Every one, no exceptions. Let things size themselves. If you absolutely must impose a fixed height on a control, consider putting its contents in a ViewBox, so the contents can dynamically size themselves without overflowing the container. Or not; that can look weird. But first get your relative layout working, and then start working on cramming it down into whatever limited space you've got for it.
When you're having trouble with XAML layout, the naive impulse is to add stuff. And worst of all, to add random stuff -- "I don't know what this property means or what its value means, but maybe if I add it on this control, it'll fix what's wrong with the other one!" -- at best, the stuff you add will be harmless.
Don't do that. Remove stuff instead, then build back up. Add one thing at a time and see what it does. And add nothing without first reading the documentation on it. Adding six random properties from Intellisense seems to take less time than looking up one property on MSDN, but that turns out not to be the case in practice, because the first approach is always guaranteed to be a total waste of time. It's like driving by closing your eyes and trying to steer by the feel of the obstacles you crash into.
You're assigning the right format string to the wrong property. Try this:
<TextBlock Text="{Binding XPath=UnitStrength, StringFormat=N0}"
Except whoops LOL ha ha that doesn't work with Binding.XPath, so I'm talking nonsense. And neither does this:
<Label Content="{Binding XPath=UnitStrength}" ContentStringFormat="N0" />
I suspect they're failing because you're giving them a string rather than an integer, but that's just a guess.
But this works.
public class IntToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int n = 0;
if (Int32.TryParse((string)value, out n))
{
value = n.ToString((String)parameter);
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML. YOUR_NAMESPACE_HERE is the C# namespace where you defined the IntToStringConverter class. That doesn't necessarily have to be right there; it could be on the parent tag or any containing tag in this XAML file, including the root tag (Window or UserControl or ResourceDictionary). Putting it here makes the example more self-contained.
<HierarchicalDataTemplate
xmlns:local="clr-namespace:YOUR_NAMESPACE_HERE"
>
<HierarchicalDataTemplate.Resources>
<local:IntToStringConverter
x:Key="IntToString"
/>
</HierarchicalDataTemplate.Resources>
<!-- blah blah -->
<TextBlock
Text="{Binding XPath=UnitStrength, Converter={StaticResource IntToString}, ConverterParameter=N0}"
/>
Update
<Window.Resources>
<!-- stuff -->
<HierarchicalDataTemplate
x:Key="UnitTemplate"
ItemsSource="{Binding XPath=Unit}"
>
<Grid Width="auto">
<!-- stuff -->
</Grid>
</HierarchicalDataTemplate>
<!-- stuff -->
</Window.Resources>
And for the TreeView:
<TreeView
...
ItemTemplate="{StaticResource UnitTemplate}"
...
/>
But this works too, if a template is going to be used in only one place:
<TreeView
...
>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate
ItemsSource="{Binding XPath=Unit}"
>
<Grid Width="auto">
<!-- stuff -->
</Grid>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Another Upate
Or finally, if you want to put all your data templates in a file of their own, you want to create a resource dictionary:
<Window.Resources>
<!-- If you're doing the merged thing, you have to explicitly have the
ResourceDictionary tag here.
-->
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="DataTemplates.xaml" />
</ResourceDictionary.MergedDictionaries>
<!-- other resources maybe -->
</ResourceDictionary>
</Window.Resources>
DataTemplate.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ScenarioEditor"
>
<local:IntToStringConverter
x:Key="IntToString"
/>
<HierarchicalDataTemplate
x:Key="UnitTemplate"
ItemsSource="{Binding XPath=Unit}"
>
<Grid Width="auto">
<!-- stuff -->
</Grid>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate
x:Key="SomeOtherTemplate"
>
<Grid Width="auto">
<!-- different stuff -->
</Grid>
</HierarchicalDataTemplate>
</ResourceDictionary>
Yet Another Update
So the tree we're looking at has multiple levels, with a different template for each level. There are two ways to do this, at least: If we had a tree of .NET classes with different child types, we could define "implicit templates" in a resource dictionary. They'd have a DataType attribute rather than x:Key, with the result that (for example) the template with DataType="{x:Type local:HQ}" would automatically be used to display any class instance of that type.
But you've got XML so that's not going to work. What you can do instead is give each HierarchicalDataTemplate its own ItemTemplate. For clarity, the following example omits ItemsSource and much else -- it only illustrates how we set up those parent/child template relationships.
<HierarchicalDataTemplate
x:Key="UnitTemplate"
>
<Grid>
<!-- Unit stuff -->
</Grid>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate
x:Key="CommanderTemplate"
ItemTemplate="{StaticResource UnitTemplate}"
>
<Grid>
<!-- Commander stuff -->
</Grid>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate
x:Key="HQTemplate"
ItemTemplate="{StaticResource CommanderTemplate}"
>
<Grid>
<!-- HQ stuff -->
</Grid>
</HierarchicalDataTemplate>
HQTemplate will be the treeview's ItemTemplate
<TreeView
...
ItemTemplate="{StaticResource HQTemplate}"
I use a GridView to display photos and I search an elegant way to allow user to add a new item to a form.
The form contains a lot of fields: it is displayed in a Pivot, where each PivotItem represents a category of the form.
Some categories contain one or more child items: they are displayed through a Master-Detail page.
It's in this page that I need to display a list of photos: as a photo represents a "sub sub item" of the form, I wouldn't manage the add of a new photo through the CommandBar. But I would like to use an "Add" button after the last item of the GridView containing the photos.
At this time I only found a solution that partially work:
Here is the XAML:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="Photos" Grid.Row="0"/>
<StackPanel Orientation="Horizontal" Grid.Row="1">
<GridView ItemsSource="{Binding images}">
<GridView.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Gray" BorderThickness="1"
Padding="10"
Height="150" Width="190">
<Image Stretch="UniformToFill"
Source="{Binding bitmap_image}" />
</Border>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
<Border BorderBrush="Gray" BorderThickness="1"
Padding="10"
Height="150" Width="190">
<Button Command="{Binding Path=DataContext.AddPhotoCommand, ElementName=DetailsPage}"
Height="100" Width="100">
<Viewbox>
<SymbolIcon Symbol="Add"/>
</Viewbox>
</Button>
</Border>
</StackPanel>
</Grid>
As I use a StackPanel, the Add button is no longer visible if I display 3 photos...
=> Is there a better way to do this? Or do you see a an alternative? I'm looking for doing this through a DataTemplateSelector, but that would require me to create a "false" object for displaying the add button...
As I use a StackPanel, the Add button is no longer visible if I display 3 photos...
If you don't mind the button is in the next line of your last photo, you can use WinRTXamlToolkit's WrapPanel instead of StackPanel to avoid the pictures goes out of the window and put the button inside the GridView's FooterTemplate:
Xaml:
<Page
x:Class="AddButtonSample.MainPage"
xmlns:controls="using:WinRTXamlToolkit.Controls"
...
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="Photos" Grid.Row="0"/>
<controls:WrapPanel Orientation="Horizontal" Grid.Row="1">
<GridView ItemsSource="{Binding images}">
<GridView.FooterTemplate>
<DataTemplate>
<Button Command="{Binding Path=AddPhotoCommand}" Height="100" Width="100">
<Viewbox>
<SymbolIcon Symbol="Add"/>
</Viewbox>
</Button>
</DataTemplate>
</GridView.FooterTemplate>
<GridView.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Gray" BorderThickness="1"
Padding="10"
Height="150" Width="190">
<Image Stretch="UniformToFill"
Source="{Binding bitmap_image}" />
</Border>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</controls:WrapPanel>
</Grid>
Result:
If you really want to put the Button side by side after the last item of GridView. The only Option is DataTemplateSelector.
The best solution might be to use a CommandBar and put the "Add" button in the very bottom of the panel, as that would be most consistent with UWP design guidelines. GridView also has a FooterTemplate, which allows you to add some XAML in the footer of the whole GridView (but not directly after the items).
If you still want to have the add item as part of the GridView contents, you will really need to use the fake item and a DataTemplateSelector. This solution is not very clean, but probably is the only simple way.
I tried to do something similar for my own app too, but there really isn't an obvious way to achieve it. A little background on what my app does: it's a flashcard app that displays decks of card in a gridview on the homepage, with an add button being at the front of the gridview. This is what I did:
for my deck class, I gave it a bool attribute isButton
in the observablecollection of decks, set the first item's isButton to true
make two datatemplates for the gridview (deck and button) and make a data template picker for the gridview, and check the isButton attribute
if isButton is true, the template picker will use the button template
otherwise use deck template
Is there a way to dock a Windows Phone control to the bottom of a StackPanel? This is my basic layout:
<StackPanel Orientation="Vertical">
<TextBlock />
<TextBlock />
<UI:AdControl />
</StackPanel>
My StackPanel is filling up the whole window. The TextBlocks are aligned toward the top of the panel, that's what I want.
But I want the AdControl to dock at the bottom of the StackPanel (and therefore the bottom of the window).
I've found controls like DockPanel, but they seem to only work for WPF or Silverlight as far as I can tell.
This can easily be accomplished with a simple grid
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<Grid.RowDefinitions>
<StackPanel>
<TextBlock />
<TextBlock />
</StackPanel>
<UI:AdControl Grid.Row="1"/>
</Grid>
If you want exactly DockPanel for future use, make sure you look into open source implementations, there should be plenty of those out there!
i need a registeration form within my application, i need scrolling so i did the following
<ScrollViewer VerticalScrollBarVisibility="Visible" Height="780" MaxHeight="1800"
MaxWidth="477" VerticalAlignment="Top">
<ScrollViewer.Content>
<Grid Width="477" Height="728" MaxHeight="1800">
<!-- .......Form's Elements..... -->
</Grid>
</ScrollViewer.Content>
</ScrollViewer>
there is no scrolling, what am i missing?
You shouldn't set the height properties when working with a ScrollViewer. If you strip it down to just the following and it still doesn't work, then it is something else in your project that is preventing it to work.
<ScrollViewer>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="Hello" FontSize="320" />
<TextBlock Grid.Row="1" Text="World" FontSize="320" />
</Grid>
</ScrollViewer>
Remove the ScrollViewer.Content - part, I have an app that has this structure and it works fine:
<ScrollViewer x:Name="ContentScrollViewer" Margin="0,0,0,8">
<Grid Height="562">
<!-- My elements -->
</Grid>
</ScrollViewer>
Your scrollviewer has a height of 780 and your grid is only 728. Why would there be any scrolling? You'll only be able to scroll if the grid is higher than 780.