Reuse large quantity of Xaml interface elements - xaml

I have made a very similar post about this earlier where I asked how to reuse a part of some Xaml code. Although that worked for the small element I had, it does not quite work on the current scale.
Using Xamarin, I have a tabbed layout with two pages. A page consists of a grid with three layers, two of which are the same on both pages. The third layer needs to be different on both pages. Both pages are currently in the same file. This means that a large portion of the code is simply a copy-paste, and it makes it more difficult to edit values of certain labels in those first two rows since they can't have similar IDs / Names.
I currently set a string variable in the codebehind with a setter.
string _vesselCode;
public string VesselCode
{
get
{
return _vesselCode;
}
set
{
_vesselCode = value;
currentVessel.Text = _vesselCode;
currentVesselClone.Text = currentVessel.Text;
}
}
Although this works, I still have a lot of duped code (as shown below).
This is the content of each page. The only page-specific content is at the bottom. The rest is literately copy-pasted to the second page. Code below is without any IDs.
<Grid RowSpacing="0">
<Grid.RowDefinitions>
<!-- first row from top {0} - app name, vessel. -->
<RowDefinition Height="Auto"/>
<!-- second row {1} - originele freq // nieuwe frequentie. -->
<RowDefinition Height="Auto"/>
<!-- third row {2} - tablayout. -->
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- header contents -->
<Grid Grid.Row="0" StyleClass="headerBar">
<StackLayout Grid.Column="0" HorizontalOptions="Start" StyleClass="inGrid">
<Label StyleClass="header"
Text="OV Frequentie"/>
<Label StyleClass="headerSub"
Text="huidig voertuig:"/>
</StackLayout>
</Grid>
<!-- freq bar contents -->
<Grid Grid.Row="1" StyleClass="subHeaderBar">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<BoxView Grid.ColumnSpan="2"
BackgroundColor="#2d313a"
WidthRequest="2"
HeightRequest="2"
Margin="5,0,5,-3"/>
<StackLayout Grid.Column="0" HorizontalOptions="Start" StyleClass="inGrid">
<Label StyleClass="generalSmallText"
Text="originele freq: 10"/>
</StackLayout>
<StackLayout Grid.Column="1" HorizontalOptions="End" StyleClass="inGrid">
<Label StyleClass="generalSmallText"
Text="nieuwe freq: 10"/>
</StackLayout>
</Grid>
<!-- info page content -->
<Grid Grid.Row="2">
<StackLayout StyleClass="infoDisplayPage">
<!-- page unique stuff -->
</StackLayout>
</Grid>
</Grid>
Like I said, this sort of works, but is incredibly bad code. There are multiple Labels I want to be able to change the value of, but I can't give them the same ID. This means I have to either always set those values twice, or get some set attribute on one of the Labels, automatically setting the other one / have attributes in the code behind with such setter.
I got told that for using bigger area's of code, I could use a ContentView. However, I can't seem to reuse them across two ContentPages.
How do I make it so I can reuse the code of the first two grid rows on both pages, and be able to have different contents of the third grid layout? I'm OK with having to possibly set both Labels using the codebehind as above if that is needed.
I'm quite new to app development, so any extra source of explenation of the code below is appriciated.

Related

ListView cells not behaving as expected with caching options and IsEnabled settings on elements within ViewCells

I have a listview that displays some information on the users payment history, and within each payment entry there are various elements that are displayed or not depending on the PaymentEntry object. The variables in this object are populated from an API call that returns Json then deserializes it into an observablelist of PaymentEntry type.
The elements in the listview cells can vary depending on two bools within the entries individually. So one might show a stacklayout that contains two labels, and another entry might not display that. I have the IsEnabled variable of these elements bound to the bools within the list of entries, as I'd expect the listview would be able to dynamically display the viewcells no matter if some of the labels inside the viewcell are disabled or not.
My caching method is RetainElement due to the variable nature of the cells.
Now, the problem is that the binding for IsEnabled doesn't seem to be working. All of the entries are enabled, regardless of whether or not the bool is true or false.
Another problem is that the caching strategy seems to be breaking the listview, it displays only about 8 entries with their elements visible, then the rest of the viewcells are all equal size and completely empty of the labels that should be inside them, whether they have a binding for IsVisible/IsEnabled or not.
Some extra background is that some elements are displayed/hidden with a converter on the bool, a negatebooleanconverter that I'm using.
<ListView
x:Name="MyListView"
CachingStrategy="RetainElement"
HasUnevenRows="True"
ItemsSource="{Binding items}">
<Label
Margin="2.5"
FontAttributes="Bold"
HorizontalOptions="StartAndExpand"
Text="{Binding issuedDate}"
VerticalOptions="FillAndExpand" />
<StackLayout
HorizontalOptions="FillAndExpand"
IsVisible="{Binding isBoolOne, Converter={Helpers1:InverseBoolConverter}}"
Orientation="Horizontal"
VerticalOptions="StartAndExpand">
<StackLayout HorizontalOptions="StartAndExpand" VerticalOptions="FillAndExpand">
<Label
FontSize="Small"
Text="Sample Text"
TextColor="RoyalBlue" />
<Label FontSize="Default" Text="{Binding SampleStringOne}" />
</StackLayout>
<StackLayout HorizontalOptions="EndAndExpand" VerticalOptions="FillAndExpand">
<Label
FontSize="Small"
HorizontalTextAlignment="End"
Text="Sample Date"
TextColor="RoyalBlue" />
<Label
FontSize="Default"
HorizontalTextAlignment="End"
Text="{Binding SampleDate}" />
</StackLayout>
<StackLayout
HorizontalOptions="FillAndExpand"
IsVisible="{Binding isBoolTwo}"
VerticalOptions="StartAndExpand">
<Label
FontSize="Small"
HorizontalTextAlignment="Start"
Text="Sample Number"
TextColor="RoyalBlue" />
<Label
FontSize="Default"
HorizontalTextAlignment="Start"
Text="{Binding SampleStringTwo}" />
</StackLayout>
<StackLayout
HorizontalOptions="FillAndExpand"
IsVisible="{Binding isBoolOne}"
VerticalOptions="StartAndExpand">
<Label
FontSize="Default"
HorizontalTextAlignment="Start"
Text="{Binding SampleStringOne}" />
</StackLayout>
Expected result is that the listview will display the cells with varying size based on the data they have enabled. So, for example, if isBoolOne is true, the first stack layout would be disabled, and if isBoolTwo is true, the next stacklayout should be enabled, and since isBoolOne is true, the last stacklayout should be enabled. Instead, all 3 of these are displayed no matter what.
I've tried doing a debug to display each bool in console from the itemsource, and they are being read correctly with no nulls and varying true/false as expected. All of the string bindings display correctly. All of the bool bindings display true apparently.
The second problem is that with caching strategy of retainelement, only the first few elements are visible, the rest are blank. With recycle element they all display, but as I understand this would prevent variation of elements enabled in each.
EDIT: I should make clear I have omitted some of the XAML, including the ListView.ItemTemplate, DataTemplate, ViewCell, a Frame, and an AbsoluteLayout that encompass what I've shown. I did this to minimize the code shown and only display the problem areas.

Issues with child page's display when using Frames

Problem
This is the layout of my main pane:
<Page
x:Class="Communities.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Communities"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" Loaded="Page_Loaded" DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="48" />
<RowDefinition Height="*" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
...
<SplitView Grid.Row="1" Name="hamburgerMenu" OpenPaneLength="200" PaneBackground="#F02A2A2A">
<SplitView.Pane>
<ListView ItemsSource="{Binding}" IsItemClickEnabled="True" ItemClick="HamburgerItemClick">
... </ListView>
</SplitView.Pane>
<Frame Name="frame" />
</SplitView>
<Grid Grid.RowSpan="3" Name="popupArea" />
</Grid>
</Page>
the frame is where I load all my pages, so that the layout is always consistant.
Now, in most of my child pages I have defined AppBar control and attached it to the BottomAppBar property of that child page:
PostView.xaml
...
<Page.BottomAppBar>
<CommandBar>
<AppBarButton Label="Back" Icon="Back" Click="TryGoBack" />
<AppBarButton Label="Refresh" Icon="Refresh" Click="TryRefreshComments" />
<AppBarButton Label="Go to Community" Icon="Go" Click="TryOpenCommunity" />
</CommandBar>
</Page.BottomAppBar>
...
Here's where the trouble starts. It works fine on PC, as the layout is mostly static on desktop. There are no software keyboards required most of the time etc. On mobile it's more problematic: Screenshots
My thoughts
It seems like the frame that is used to display the child page is causing all sorts of problems. When the AppBar is defined in the main page it positions correctly.
I'd like to avoid the keyboard covering the textbox as well as the AppBar but I don't want to get rid of the frame control. I'd also prefer it if the page got "squished" when the keyboard shows up, instead of getting pushed upwards, but I'm not sure how to display the keyboard on the frame level, instead of the entire MainPage, default level.
What would be the best way to solve this situation?
Cheers!
As you know, if we set the Page.BottomAppBar in the root of the Page, there is no issue with Touch keyboard. It seems it is the best way to add the Page.BottomAppBar.
If you want to add the Page.BottomAppBar in the other page in the Frame, you should be able to customize your UI. The UWP provides similar behavior on the appearance of the touch keyboard by handling the Showing and Hiding events exposed by the InputPane object.
We can use the InputPaneVisibilityEventArgs.OccludedRect to get the region of the application's window that the input pane is covering.
For example:
public PostView()
{
this.InitializeComponent();
InputPane.GetForCurrentView().Showing += PostView_Showing;
InputPane.GetForCurrentView().Hiding += PostView_Hiding;
}
private void PostView_Hiding(InputPane sender, InputPaneVisibilityEventArgs args)
{
MyTextBox.Margin = new Thickness(0, args.OccludedRect.Height, 0, 0);
}
private void PostView_Showing(InputPane sender, InputPaneVisibilityEventArgs args)
{
MyTextBox.Margin = new Thickness(0, 0, 0, args.OccludedRect.Height);
}

How to show ProgressRing in UWP app with Hub control

I have a UWP app that needs to get SQL data from a webservice hosted on a .Net server in the cloud as well as some extra data from a web API and I want to show an indeterminate Progress Ring control while it's loading.
I have the control bound to a boolean in my MainViewModel that I set to true at the start of the whole update process (done via an awaited async method which works perfectly) and then to false when all data is downloaded but the ring just never becomes visible. I can see PropertyChanged is getting raised but it makes no difference - the progress ring just never displays.
Here's the thing though - the progress ring IS working, but because I can't get it to display in front of my Hub control it just appears not to be working. I know this because if I move the Hub down 100 pixels and set the vertical alignment of the progress ring to Top I can then see the ring and it behaves exactly as expected. But as I obviously don't want to waste 100 pixels I need to get it to display at the front of all content as you'd expect any progress control to do.
I have also tried putting the ring in the grid of the data template's grid for the middle hub section (there are 3 side by side at 640px each) and in the DataTmeplate itself (the second one in Page.Resources below) but it still won't display it so I am tearing my hair out.
I know this has got to be the simplest XAML issue but I've tried everything I can think of and searched the web so I'm hoping someone else can help me get the progess ring to display in front of the Hub?
Here's a cut down version of my XAML page - I can post the lot but it's a big page.
<Page
x:Class="TVTracker.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:TVTracker"
xmlns:vm="using:TVTracker.ViewModel"
xmlns:model="using:TVTracker.Model"
xmlns:hlp="using:TVTracker.Common"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
RequestedTheme="Light">
<Page.DataContext>
<vm:MainViewModel/>
</Page.DataContext>
<Page.Resources>
<!-- styles and other resources here -->
<!-- gets displayed in first of 3 hub sections 640 wide -->
<DataTemplate x:Key="ShowListTemplate" x:DataType="model:TVShow">
<Grid Width="640" Height="Auto" Margin="5" HorizontalAlignment="Stretch" RightTapped="ShowsList_RightTapped">
<!-- ...etc etc... -->
<\DataTemplate>
<!-- gets displayed in second of 3 hub sections 640 wide -->
<DataTemplate x:Key="DetailsTemplate" x:DataType="model:TVShow">
<Grid Width="640" Margin="0,20,0,0" Height="Auto" VerticalAlignment="Top" HorizontalAlignment="Stretch">
<!-- ...etc etc... -->
</Grid>
</DataTemplate>
<!-- gets displayed in second of 3 hub sections 640 wide -->
<DataTemplate x:Key="EpisodeListTemplate" x:DataType="model:Episode">
<Grid Width="640" Margin="0,20,0,0" Height="Auto" VerticalAlignment="Top" HorizontalAlignment="Stretch">
<!-- ...etc etc... -->
</Grid>
</DataTemplate>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ProgressRing Name="pgbLoadingShow" IsActive="{Binding LoadingShowData}" Width="100" Height="100" VerticalAlignment="top" Foreground="{StaticResource Medium}"/>
<Hub SectionHeaderClick="Hub_SectionHeaderClick" Margin="0,0,0,0" HorizontalContentAlignment="Stretch" Background="{StaticResource Dark}">
<Hub.Header>
<TextBlock Text="ShowTracker" FontSize="14" Foreground="{StaticResource Bright}"/>
</Hub.Header>
<HubSection Name="hsShows" MinWidth="640" Padding="20" VerticalAlignment="Top" Background="{StaticResource Dark}" >
<!-- ...etc etc.. -->
</HubSection>
<!-- two other hubsections here -->
</Hub>
</Grid>
</Page>
Have you tried putting the ProgressRing after the Hub in your XAML?
<Hub/>
<ProgressRing/>
Generally it will be displayed in the order which you put them in, so the ProgressRing element will be behind the Hub with your code.

Changing the size of SplitView.Pane

The basic hamburger menu using SplitView found in many examples is cool but I like the way Microsoft implemented it in some of their apps in Windows 10 such as News and Sports. The way they implemented it is when the SplitView.Pane is open, its height is not the same as the root frame's height. In other word, the Pane's height and Content's height is not the same. The benefit of this style is that full content of the pageheader of the SplitView.Content is visible. Can somebody help me out on how to achieve that effect since I am new to xaml. I hope my question is pretty clear to understand.
Thanks,
AB
On the official "sport"/"news" page, there are several elements: toggle button, SplitView and etc. In the SplitView, there are also several sub-items, such as panel and frame. There are lots of approaches to help you to get your own desired effect: you can use layout panel, such as StackPanel or Grid, to arrange those UI elements on the page; you can modify the splitview's default template; and you can also just customize frame and panel' height by setting their related properties, such as: height or Margin. For more instructions of UWP design please go here.
Below is a simple example by using Grid layout and adjusting "margin" property of splitview's frame. In this example, I put the toggled button and a back button on the page header (you can change the back button to a navigation bar later). Then adjust the "margin" property of the frame, so that it doesn't has the same height as the panel. You can get a complete sample of SplitView here.
<!-- Put the whole page content in a grid of 2*2 -->
<Grid>
<Grid.RowDefinitions >
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions >
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button x:Name="BackButton"
Grid.Row="0"
Grid.Column="1"
HorizontalAlignment="Center"
TabIndex="2"
IsEnabled="{Binding AppFrame.CanGoBack, ElementName=Root}"
Width="{Binding ItemsPanelRoot.Width, ElementName=NavMenuList}"
Click="BackButton_Click"/>
<!-- Top-level navigation menu + app content
and put the SplitView in another row to leave space for page header -->
<SplitView x:Name="RootSplitView"
DisplayMode="Inline"
OpenPaneLength="256"
IsTabStop="False" Grid.Row="1" Grid.ColumnSpan="2">
<SplitView.Pane >
<!-- A custom ListView to display the items in the pane. The automation Name is set in the ContainerContentChanging event. -->
<controls:NavMenuListView x:Name="NavMenuList"
TabIndex="3"
ContainerContentChanging="NavMenuItemContainerContentChanging"
ItemContainerStyle="{StaticResource NavMenuItemContainerStyle}"
ItemTemplate="{StaticResource NavMenuItemTemplate}"
ItemInvoked="NavMenuList_ItemInvoked">
</controls:NavMenuListView>
</SplitView.Pane>
<!-- Set Frame's margin property to differ from panel -->
<!-- OnNavigatingToPage we synchronize the selected item in the nav menu with the current page.
OnNavigatedToPage we move keyboard focus to the first item on the page after it's loaded. -->
<Frame x:Name="frame"
Navigating="OnNavigatingToPage"
Navigated="OnNavigatedToPage"
Margin="0,100,0,0" >
<Frame.ContentTransitions>
<TransitionCollection>
<NavigationThemeTransition>
<NavigationThemeTransition.DefaultNavigationTransitionInfo>
<EntranceNavigationTransitionInfo/>
</NavigationThemeTransition.DefaultNavigationTransitionInfo>
</NavigationThemeTransition>
</TransitionCollection>
</Frame.ContentTransitions>
</Frame>
</SplitView>
<!-- Declared last to have it rendered above everything else, but it needs to be the first item in the tab sequence. -->
<ToggleButton x:Name="TogglePaneButton"
TabIndex="1"
Style="{StaticResource SplitViewTogglePaneButtonStyle}"
IsChecked="{Binding IsPaneOpen, ElementName=RootSplitView, Mode=TwoWay}"
Unchecked="TogglePaneButton_Checked"
AutomationProperties.Name="Menu"
ToolTipService.ToolTip="Menu" Grid.Row="0" Grid.Column="0" />
</Grid>

Microsoft Surface - Flip & Scatter View Items

I'm currently trying to get flip to work with scatterview items, and I'm having some trouble conceptually with it, using a plugin called Thriple (http://thriple.codeplex.com/).
Essentially, a 2 sided thriple control looks like this:
<thriple:ContentControl3D
xmlns:thriple="http://thriple.codeplex.com/"
Background="LightBlue"
BorderBrush="Black"
BorderThickness="2"
MaxWidth="200" MaxHeight="200"
>
<thriple:ContentControl3D.Content>
<Button
Content="Front Side"
Command="thriple:ContentControl3D.RotateCommand"
Width="100" Height="100"
/>
</thriple:ContentControl3D.Content>
<thriple:ContentControl3D.BackContent>
<Button
Content="Back Side"
Command="thriple:ContentControl3D.RotateCommand"
Width="100" Height="100"
/>
</thriple:ContentControl3D.BackContent>
</thriple:ContentControl3D>
What I'm struggling to grasp is if I should be making 2 separate ScatterView templates to bind to the data I want, and then each one would be the "front" and "back" of a scatterview item OR should i make 2 separate ScatterView items which are bound to the data I want, which is then bound to the "back" and "front" of a main ScatterView item?
If there is a better way of using doing flip animations with ScatterViewItem's, that'd be cool too!
Thanks!
I would create two separate templates for the data. To the user, it is still the same scatterviewitem (with two sides) so having them as two separate items makes little sense. You can specify which templates to use for front vs back in the properties of the ContentControl3D class. (They are of type DataTemplate)
Code-wise, it'd look something like this:
<thriple:ContentControl3D
xmlns:thriple="http://thriple.codeplex.com/"
Background="LightBlue"
BorderBrush="Black"
BorderThickness="2"
MaxWidth="200" MaxHeight="200"
Content="{Binding MyData}"
BackContent="{Binding MyData}"
ContentTemplate="{StaticResource MyFrontTemplate}"
BackContentTemplate="{StaticResource MyBackTemplate}"
/>
You could also just specify the content directly in the declaration of the control (like you have your buttons above) if that makes more sense for you. That saves you from having to create data templates for the content:
<thriple:ContentControl3D
xmlns:thriple="http://thriple.codeplex.com/"
Background="LightBlue"
BorderBrush="Black"
BorderThickness="2"
MaxWidth="200" MaxHeight="200"
>
<thriple:ContentControl3D.Content>
<Grid>
<TextBlock Text="I'm the front" />
<TextBlock Text="{Binding SomeDataProperty}" />
<Button
Content="Flip"
Command="thriple:ContentControl3D.RotateCommand"
Width="100" Height="100"
/>
</Grid>
<thriple:ContentControl3D.BackContent>
<Grid>
<TextBlock Text="I'm the back" />
<TextBlock Text="{Binding SomeOtherDataProperty}" />
<Button
Content="Flip"
Command="thriple:ContentControl3D.RotateCommand"
Width="100" Height="100"
/>
</Grid>
</thriple:ContentControl3D.BackContent>
</thriple:ContentControl3D>