How to hide top part of hamburger menu in the XAML Navigation Sample? - xaml

In the Windows UWP XAML Navigation sample from git hub, how can you hide the very top part of the hamburger menu flyout that obscures the section title?
Currently it renders like this so there is a strip that hides the section title of the page.
How can I get it to look like this? So the Section title is not obscured when I open the menu.
I tried playing with the z-index of the page header, but it had no effect. The hamburger menu always renders over top everything else.

Just check the Microsoft weather app for windows 10, I think it's more like there is a region out of the SplitView control, which is to hold like "hamburger button", "back button", "commandbar", and "AutoSuggestBox".
Here I wrote a sample:
<Page.Resources>
<local:BoolToVisiableConverter x:Key="visiblecvt" />
<local:BackgroundConverter x:Key="backgroundcvt" />
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="15*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0" Background="{Binding ElementName=listmenu, Path=SelectedItem.MenuText, Converter={StaticResource backgroundcvt}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<Button BorderThickness="0" Background="LightBlue" Click="Button_Click_Pane" Grid.Column="0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Button.Content>
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="24" />
</Button.Content>
</Button>
<Button BorderThickness="0" Background="Transparent" Click="Button_Click_Back" Grid.Column="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Button.Content>
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="24" />
</Button.Content>
</Button>
<TextBlock FontSize="24" Grid.Column="2" x:Name="title" VerticalAlignment="Center" Text="{Binding ElementName=listmenu, Path=SelectedItem.MenuText}" />
<CommandBar Grid.Column="3" Background="Transparent" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Visibility="{Binding ElementName=title, Path=Text, Converter={StaticResource visiblecvt}}">
<CommandBar.Content>
<Grid />
</CommandBar.Content>
<AppBarButton Icon="Accept" FontSize="24" Label="Accept" />
<AppBarButton Icon="Cancel" FontSize="24" Label="Cancel" />
</CommandBar>
<AutoSuggestBox Grid.Column="4" VerticalAlignment="Center" HorizontalAlignment="Stretch" IsSuggestionListOpen="True" />
</Grid>
<SplitView Grid.Row="1" x:Name="RootSpiltView" OpenPaneLength="300" CompactPaneLength="50" DisplayMode="CompactOverlay">
<SplitView.Pane>
<ListView x:Name="listmenu" ItemsSource="{x:Bind menu}" SelectionChanged="ListView_SelectionChanged">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding MenuIcon}" FontFamily="Segoe MDL2 Assets" FontSize="24" VerticalAlignment="Center" />
<TextBlock Text="{Binding MenuText}" Margin="15" VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</SplitView.Pane>
<SplitView.Content>
<Frame x:Name="splitviewContent" Navigated="splitviewContent_Navigated" />
</SplitView.Content>
</SplitView>
</Grid>
code behind:
private ObservableCollection<NavigationItem> menu = new ObservableCollection<NavigationItem>();
public MainPage()
{
this.InitializeComponent();
this.Loaded += MainPage_Loaded;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
menu.Clear();
menu.Add(new NavigationItem { PageLink = typeof(Page1), MenuText = typeof(Page1).Name, MenuIcon = "\xE715" });
menu.Add(new NavigationItem { PageLink = typeof(Page2), MenuText = typeof(Page2).Name, MenuIcon = "\xE716" });
menu.Add(new NavigationItem { PageLink = typeof(Page3), MenuText = typeof(Page3).Name, MenuIcon = "\xE722" });
menu.Add(new NavigationItem { PageLink = typeof(Page4), MenuText = typeof(Page4).Name, MenuIcon = "\xE72D" });
}
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
listmenu.SelectedIndex = 0;
}
private void Button_Click_Pane(object sender, RoutedEventArgs e)
{
this.RootSpiltView.IsPaneOpen = !this.RootSpiltView.IsPaneOpen;
}
private void Button_Click_Back(object sender, RoutedEventArgs e)
{
if (splitviewContent.CanGoBack)
{
splitviewContent.GoBack();
}
}
private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var naviitem = listmenu.SelectedItem as NavigationItem;
splitviewContent.Navigate(naviitem.PageLink);
}
private void splitviewContent_Navigated(object sender, NavigationEventArgs e)
{
var page = splitviewContent.CurrentSourcePageType.Name;
switch (page)
{
case "Page1":
listmenu.SelectedIndex = 0;
break;
case "Page2":
listmenu.SelectedIndex = 1;
break;
case "Page3":
listmenu.SelectedIndex = 2;
break;
case "Page4":
listmenu.SelectedIndex = 3;
break;
}
}
The NavigationItem class and two converters:
public class NavigationItem
{
public string MenuIcon { get; set; }
public string MenuText { get; set; }
public Type PageLink { get; set; }
}
public class BoolToVisiableConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var text = (string)value;
if (text == "Page1")
{
return Visibility.Visible;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
public class BackgroundConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var text = (string)value;
if (text == "Page1")
{
return "#FFFFC0CB";
}
return "#00000000";
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
I didn't follow to the official XAML Navigation sample to wrote this code, here my sample renders like this:
#Henk Holterman's comment also makes sense. In the official sample, the title is part of the page content. For different page, the title may have different size. But in Weather app, the title is separated from the content, so it will be easy to achieve.

Related

Bind a click event to a dynamically generated button element in a templated control / template control

I have a templated control in my UWP application which contains a ListView. The ListView is populated in the runtime.
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Renderer"
xmlns:triggers="using:Microsoft.Toolkit.Uwp.UI.Triggers">
<Style x:Key="RendererDefaultStyle" TargetType="local:Renderer" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:Renderer">
<Grid>
....
<ListView x:Name="AnnotsList" Margin="0,12,0,0" SelectionMode="None" Grid.Row="1" VerticalAlignment="Stretch" IsItemClickEnabled="True" Visibility="{Binding IsAnnotationsListOpen, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" ItemContainerStyle="{StaticResource AnnotationsListViewItemStyle}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding}" />
<TextBlock Text="{Binding DisplayTitle}" Margin="20,0,0,10" FontSize="12" TextWrapping="WrapWholeWords" Visibility="Visible" />
</StackPanel>
<CommandBar Grid.Column="1">
<CommandBar.SecondaryCommands>
<AppBarElementContainer>
<StackPanel Orientation="Horizontal">
<Button x:Name="btn_RemoveFromList" DataContext="{Binding}">
<Button.Content>
<SymbolIcon Symbol="Delete" />
</Button.Content>
<ToolTipService.ToolTip>
<ToolTip Content="Delete" Placement="Mouse" />
</ToolTipService.ToolTip>
</Button>
</StackPanel>
</AppBarElementContainer>
</CommandBar.SecondaryCommands>
</CommandBar>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.GroupStyle>
<GroupStyle >
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Border AutomationProperties.Name="{Binding Key}">
<TextBlock Text="{Binding Key}" Style="{ThemeResource TitleTextBlockStyle}"/>
</Border>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
....
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="local:Renderer" BasedOn="{StaticResource RendererDefaultStyle}"/>
</ResourceDictionary>
I tried to bind a click event to the button like this but since it is dynamically generated it doesn't work.
public sealed class Renderer: Control, IDisposable
{
....
private void UpdateAnnotationsListView()
{
(GetTemplateChild("AnnotsList") as ListView).ItemsSource = null;
var source = AnnotationAdapter.GetGroupedAnnotations(); // ObservableCollection<ListViewGroupInfo>
var viewSource = new CollectionViewSource
{
IsSourceGrouped = true, Source = source
};
(GetTemplateChild("AnnotsList") as ListView).ItemsSource = viewSource.View;
if (viewSource.View.Count > 0)
{
(GetTemplateChild("btn_RemoveFromList") as Button).Click -= null;
(GetTemplateChild("btn_RemoveFromList") as Button).Click += async delegate(object sender, RoutedEventArgs e)
{
await OpenRemoveConfirmationAsync();
};
}
}
....
}
List source is a ObservableCollection of type
public class ListViewGroupInfo: List < object >
{
public ListViewGroupInfo() {}
public ListViewGroupInfo(IEnumerable < object > items): base(items) {}
public object Key
{
get;
set;
}
}
List source is structured in such a way where I can group the list items accordingly.
This is a sample of the rendered ListView for more context.
The Delete buttons are the ones I'm trying to work with here.
I want to bind a method to the click event of those buttons in the ListView.
I Cannot use the name attribute since there can be multiple buttons as the list grows.
Since this button is in a templated control & generated in the runtime, I couldn't find a way to bind a method to the click event.
My guess is that I will have to bind a command to the button. But I couldn't find a way to do that either.
I did not use MVVM pattern in the templated control.
Could anyone help me with this? Any help is much appreciated.
My guess is that I will have to bind a command to the button. But I couldn't find a way to do that either.
The better way is using command to approach, I will share the detail steps below that you could refer to. please note you need to set current page datacontext as this this.DataContext = this;. it could make sure you can access command where in the code behind from DataTemplate.
Xaml Code
<Grid>
<ListView x:Name="MyListView" ItemsSource="{x:Bind Items}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Header}" />
<TextBlock
Margin="20,0,0,10"
FontSize="12"
Text="{Binding DisplayTitle}"
TextWrapping="WrapWholeWords"
Visibility="Visible" />
</StackPanel>
<CommandBar Grid.Column="1">
<CommandBar.SecondaryCommands>
<AppBarElementContainer>
<StackPanel Orientation="Horizontal">
<Button
x:Name="btn_RemoveFromList"
Command="{Binding DataContext.DeleteCommand, ElementName=MyListView}"
CommandParameter="{Binding}">
<Button.Content>
<SymbolIcon Symbol="Delete" />
</Button.Content>
<ToolTipService.ToolTip>
<ToolTip Content="Delete" Placement="Mouse" />
</ToolTipService.ToolTip>
</Button>
</StackPanel>
</AppBarElementContainer>
</CommandBar.SecondaryCommands>
</CommandBar>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
Code behind
public sealed partial class ListPage : Page
{
public ListPage()
{
this.InitializeComponent();
this.DataContext = this;
}
private ObservableCollection<Model> Items { set; get; }
public ICommand DeleteCommand
{
get
{
return new CommadEventHandler<Model>((s) =>
{
Items.Remove(s);
});
}
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
MakeDataSource();
}
private void MakeDataSource()
{
Items = new ObservableCollection<Model>();
for (int i = 0; i < 10; i++)
{
Items.Add(new Model()
{
Header = $"header{i}",
DisplayTitle= $"DisplayTitle{i}"
});
}
}
}
public class Model
{
public string Header { get; set; }
public string DisplayTitle { get; set; }
}
class CommadEventHandler<T> : ICommand
{
public event EventHandler CanExecuteChanged;
public Action<T> action;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
this.action((T)parameter);
}
public CommadEventHandler(Action<T> action)
{
this.action = action;
}
}
After a whole bunch of research & trial and error, I ended up with a different approach as #nico-zhu-msft suggested.
Basically, I moved the ListView to a separate user control & observed property changes from the parent template control. In order to bind data to the ListView used a view-model.
AssnotationsList.xaml
<UserControl
x:Class="PDF.Renderer.AnnotationsList"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:PDF.Renderer"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewmodels="using:PDF.Renderer.ViewModels"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<UserControl.DataContext>
<viewmodels:AnnotationsListViewModel />
</UserControl.DataContext>
<UserControl.Resources>
<Style x:Key="AnnotationsListViewItemStyle" TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
</UserControl.Resources>
<ListView SelectionMode="None" VerticalAlignment="Stretch" IsItemClickEnabled="True" ItemContainerStyle="{StaticResource AnnotationsListViewItemStyle}" ItemsSource="{Binding AnnotationsList}" ItemClick="AnnotationListViewItemClick">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding}" />
<TextBlock Text="{Binding DisplayTitle}" Margin="20,0,0,10" FontSize="12" TextWrapping="WrapWholeWords" Visibility="Visible" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.GroupStyle>
<GroupStyle >
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Border AutomationProperties.Name="{Binding Key}">
<TextBlock Text="{Binding Key}" Style="{ThemeResource TitleTextBlockStyle}"/>
</Border>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
</UserControl>
AnnotationsList.xaml.cs
public sealed partial class AnnotationsList : UserControl, INotifyPropertyChanged
{
public AnnotationsList()
{
this.InitializeComponent();
}
private BaseAnnotation selectedAnnotation = null;
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
public ICollectionView AnnotationsListSource
{
get { return (ICollectionView)GetValue(AnnotationsListSourceProperty); }
set { SetValue(AnnotationsListSourceProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AnnotationsListSourceProperty =
DependencyProperty.Register(nameof(AnnotationsListSourceProperty), typeof(ICollectionView), typeof(AnnotationsList), new PropertyMetadata(null, new PropertyChangedCallback(OnAnnotationsListSourceChanged)));
private static void OnAnnotationsListSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (object.Equals(e.NewValue, e.OldValue) || e.NewValue is null)
return;
d.RegisterPropertyChangedCallback(AnnotationsListSourceProperty, CaptureAnnotationListSource);
}
private static void CaptureAnnotationListSource(DependencyObject sender, DependencyProperty dp) => (sender as AnnotationsList).SetAnnotationsListSource(sender.GetValue(dp) as ICollectionView);
private void SetAnnotationsListSource(ICollectionView annotationsCollection) => (this.DataContext as AnnotationsListViewModel).AnnotationsList = annotationsCollection;
public BaseAnnotation SelectedAnnotation
{
get { return selectedAnnotation; }
set { if (value != selectedAnnotation && value != null) { selectedAnnotation = value; OnPropertyChanged(nameof(SelectedAnnotation)); }; }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedAnnotationProperty =
DependencyProperty.Register(nameof(SelectedAnnotationProperty), typeof(BaseAnnotation), typeof(AnnotationsList), new PropertyMetadata(null));
private void AnnotationListViewItemClick(object sender, ItemClickEventArgs e) => SelectedAnnotation = e.ClickedItem as BaseAnnotation;
}
AnnotationsListViewModel.cs
class AnnotationsListViewModel : ViewModalBase
{
private ICollectionView annotationsList = null;
public ICollectionView AnnotationsList
{
get { return annotationsList; }
set { if(value != annotationsList) { annotationsList = value; OnPropertyChanged(nameof(AnnotationsList)); } }
}
}
Replaced the ListView with the user control Renderer.cs like this.
<local:AnnotationsList x:Name="ctrl_AnnotationsList" Margin="0,12,0,0" Grid.Row="1" VerticalAlignment="Stretch" Visibility="{Binding IsAnnotationsListOpen, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" />
In the parent control class Renderer.cs (template control) got a reference to the AnnotationsList control like this when parent is first rendered & bound the PropertyChanged event.
AnnotationsList = GetTemplateChild("ctrl_AnnotationsList") as AnnotationsList;
AnnotationsList.PropertyChanged -= null;
AnnotationsList.PropertyChanged += OnAnnotationsListPropertyChanged;
Added the following code to trigger on property changes in the AnnotationsList control.
private void OnAnnotationsListPropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch(e.PropertyName)
{
case "SelectedAnnotation":
var annotation = (sender as AnnotationsList).SelectedAnnotation;
if (annotation != null)
GoToAnnotation(annotation).GetAwaiter();
break;
default:
break;
}
}
For now it is configured to trigger on ItemClick event of the ListViewItems.
Hope this helps someone who might be looking for a similar solution.

Refresh SemanticZoom ObservableCollection in ViewModel

When using a SemanticZoom control, is there a way to update the ObservableCollection in the ViewModel after a table change? After making changes to the table in SQLite, within the same page (categories.xaml.cs), the SemanticZoom control does not update. Reloading the page from menu navigation does reload the page with the correct data. If the control just took an ObservableCollection as it's items source, the ObservableCollection could just be refreshed. Using a ViewModel was the only code example I could find for the SemanticZoom control. Thanks in advance!
categories.xaml
<Page.DataContext>
<vm:CategoriesViewModel></vm:CategoriesViewModel>
</Page.DataContext>
<Page.Resources>
<CollectionViewSource x:Name="Collection" IsSourceGrouped="true" ItemsPath="Items" Source="{Binding CategoryGroups}" />
</Page.Resources>
<SemanticZoom Name="szCategories" ScrollViewer.ZoomMode="Enabled">
<SemanticZoom.ZoomedOutView>
<GridView ScrollViewer.IsHorizontalScrollChainingEnabled="False">
<GridView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Group.Name }" Foreground="Gray" Margin="5" FontSize="25" />
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</SemanticZoom.ZoomedOutView>
<SemanticZoom.ZoomedInView>
<ListView Name="lvCategories" ItemsSource="{Binding Source={StaticResource Collection}}" Tapped="lvCategories_Tapped">
<ListView.ItemTemplate>
<DataTemplate x:DataType="data:Category">
<StackPanel>
<TextBlock Text="{Binding Title}" Margin="5" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text='{Binding Name}' Foreground="Gray" FontSize="25" Margin="5,5,5,0" />
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
</SemanticZoom.ZoomedInView>
</SemanticZoom>
categories.xaml.cs
public Categories()
{
this.InitializeComponent();
var collectionGroups = Collection.View.CollectionGroups;
((ListViewBase)this.szCategories.ZoomedOutView).ItemsSource = collectionGroups;
}
CategoriesViewModel.cs
internal class CategoriesViewModel : BindableBase
{
public CategoriesViewModel()
{
CategoryGroups = new ObservableCollection<CategoryDataGroup>(CategoryDataGenerator.GetGroupedData());
}
private ObservableCollection<CategoryDataGroup> _groups;
public ObservableCollection<CategoryDataGroup> CategoryGroups
{
get { return _groups; }
set { SetProperty(ref _groups, value); }
}
}
public abstract class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (object.Equals(storage, value)) return false;
storage = value;
this.OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged(string propertyName)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
SymanticZoom.cs
internal class CategoryDataGroup
{
public string Name { get; set; }
public List<CategoryData> Items { get; set; }
}
internal class CategoryData
{
public CategoryData(string grp, string title)
{
Grp = grp;
Title = title;
}
public string Grp { get; private set; }
public string Title { get; private set; }
}
internal class CategoryDataGenerator
{
private static List<CategoryData> _data;
public static List<CategoryDataGroup> GetGroupedData()
{
if (_data != null)
_data.Clear();
GenerateData();
return _data.GroupBy(d => d.Grp[0],
(key, items) => new CategoryDataGroup() { Name = key.ToString(), Items = items.ToList() }).ToList();
}
private static void GenerateData()
{
ObservableCollection<Category> ocCategories = new ObservableCollection<Category>();
SQLiteManager.Categories.Select(ocCategories);
_data = new List<CategoryData>();
foreach (var temp in ocCategories)
{
_data.Add(new CategoryData(temp.Name.Substring(0,1), temp.Name));
}
}
}
The zoomed-in view and zoomed-out view should be synchronized, so if a user selects a group in the zoomed-out view, the details of that same group are shown in the zoomed-in view. You can use a CollectionViewSource or add code to synchronize the views.
For more info, see Semantic zoom.
We can use CollectionViewSource control in our page, it provides a data source that adds grouping and current-item support to collection classes. Then we can bind the GridView.ItemSource and ListView.ItemSource to the CollectionViewSource. When we set new data to the CollectionViewSource, the GridView in SemanticZoom.ZoomedOutView and ListView in SemanticZoom.ZoomedInView will be updated.
xmlns:wuxdata="using:Windows.UI.Xaml.Data">
<Page.Resources>
<CollectionViewSource x:Name="ContactsCVS" IsSourceGrouped="True" />
<DataTemplate x:Key="ZoomedInTemplate" x:DataType="data:Contact">
<StackPanel Margin="20,0,0,0">
<TextBlock Text="{x:Bind Name}" />
<TextBlock Text="{x:Bind Position}" TextWrapping="Wrap" HorizontalAlignment="Left" Width="300" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="ZoomedInGroupHeaderTemplate" x:DataType="data:GroupInfoList">
<TextBlock Text="{x:Bind Key}"/>
</DataTemplate>
<DataTemplate x:Key="ZoomedOutTemplate" x:DataType="wuxdata:ICollectionViewGroup">
<TextBlock Text="{x:Bind Group.(data:GroupInfoList.Key)}" TextWrapping="Wrap"/>
</DataTemplate>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel>
<SemanticZoom x:Name="Control1" Height="500">
<SemanticZoom.ZoomedInView>
<GridView ItemsSource="{x:Bind ContactsCVS.View,Mode=OneWay}" ScrollViewer.IsHorizontalScrollChainingEnabled="False" SelectionMode="None"
ItemTemplate="{StaticResource ZoomedInTemplate}">
<GridView.GroupStyle>
<GroupStyle HeaderTemplate="{StaticResource ZoomedInGroupHeaderTemplate}" />
</GridView.GroupStyle>
</GridView>
</SemanticZoom.ZoomedInView>
<SemanticZoom.ZoomedOutView>
<ListView ItemsSource="{x:Bind ContactsCVS.View.CollectionGroups}" SelectionMode="None" ItemTemplate="{StaticResource ZoomedOutTemplate}" />
</SemanticZoom.ZoomedOutView>
</SemanticZoom>
</StackPanel>
</Grid>

How to bind to IsExpanded property - to persist the TreeView's expanded state?

In the ViewModel I have defined a bool ShouldExpand property, bound to it in from the TreeView's itemcontainerstyle - it does not work.
This answer (WinRT XAML Toolkit TreeView Save state) would've answered my question, but it does not work...
Is there a WORKING code snippet?
public class AgendaItemTreeView : ViewModelBase
{
private string _title;
private Visibility _documentGridVisibility = Visibility.Collapsed;
private bool _shouldExpand;
// Binding property - to display in a TreeView
public string Title
{
get { return _title; }
set { Set(ref _title, value); }
}
// To expand/collapse documents GridView
public Visibility DocumentGridVisibility
{
get { return _documentGridVisibility; }
set { Set(ref _documentGridVisibility, value); }
}
// Property to expand/collapse a node in a TreeView - does not work!
public bool ShouldExpand
{
get { return _shouldExpand; }
set { Set(ref _shouldExpand, value); }
}
// Nested agenda items
public ObservableCollection<AgendaItemTreeView> AgendaItems { get; set; } = new ObservableCollection<AgendaItemTreeView>();
// Documents of current agenda item
public ObservableCollection<DocumentViewModel> Documents { get; set; } = new ObservableCollection<DocumentViewModel>();
public DocumentViewModel SelectedItem { get; set; }
public AgendaItemTreeView(AgendaItem agendaItem, string selectedTitle = "")
{
// Constructor populating the nested AgendaItem & Documents
}
}
XAML:
<UserControl
<UserControl.Resources>
<!--<Style x:Key="TreeViewItemContainerStyle" TargetType="controls:TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding ShouldExpand, Mode=TwoWay}"/>
</Style>-->
<!-- Folder type Node, that can contain other 'folders' and 'files' -->
<DataTemplate x:Key="TreeViewItemTemplate" x:DataType="vm:AgendaItemTreeView">
<data:DataTemplateExtensions.Hierarchy>
<data:HierarchicalDataTemplate ItemsSource="{Binding AgendaItems}" />
<!-- When the next line used together with the Style TreeViewItemContainerStyle - the TreeView still does not expand the top level tree. The app C
<!--<data:HierarchicalDataTemplate ItemsSource="{Binding AgendaItems}" ItemContainerStyle="{StaticResource TreeViewItemContainerStyle}"/>-->
</data:DataTemplateExtensions.Hierarchy>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ToggleButton x:Name="titleToggleBtn" Tapped="{x:Bind ExpandDocumentsCommand}">
<StackPanel Orientation="Horizontal">
<SymbolIcon Symbol="{x:Bind DocumentsIcon}" Margin="5,0"/>
<TextBlock Text="{Binding Title}"/>
</StackPanel>
</ToggleButton>
<GridView Grid.Row="1" x:Name="DocumentsGrid" ItemsSource="{Binding Documents}" ItemTemplate="{StaticResource ZoomedInTemplate}" Visibility="{Binding DocumentGridVisibility}" SelectedItem="{Binding SelectedItem}"/>
</Grid>
</DataTemplate>
<!-- Nested 'file' type Node data template -->
<DataTemplate x:Key="ZoomedInTemplate" x:DataType="vm:DocumentViewModel">
<Border BorderBrush="Black" Padding="5" BorderThickness="1" Width="100">
<Grid Tapped="{x:Bind TappedCommand}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<Image Source="{Binding Thumbnail}" Width="60" Height="80" />
<TextBlock Grid.Row="1" Text="{x:Bind Title, Mode=OneWay}" VerticalAlignment="Center" Tapped="{x:Bind TappedCommand}"/>
</Grid>
</Border>
</DataTemplate>
</UserControl.Resources>
<Grid d:DataContext="{d:DesignData /SampleData/AgendaItemTreeViewSampleData.xaml}">
<controls:TreeView x:Name="myTreeView"
ItemsSource="{x:Bind TreeItems}"
ItemTemplate="{StaticResource TreeViewItemTemplate}" />
</Grid>
</UserControl>

How do I self-reference a DataTemplate in UWP XAML Resources?

I've seen something along these lines done with {DynamicResource xyz}on some other SO question, but it doesn't seem to work with UWP. Here's my XAML:
<DataTemplate x:Key="commentTemplate">
<StackPanel>
<Grid Margin="4" Background="#40606060" MinHeight="64">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<StackPanel>
<TextBlock Margin="4" FontSize="14" TextWrapping="WrapWholeWords" Text="{Binding Path=Message, Mode=OneWay}" />
<Image MaxHeight="96" Source="{Binding Path=Image}" HorizontalAlignment="Left" Margin="4" />
</StackPanel>
<StackPanel Background="#18808080" Orientation="Horizontal" Grid.Row="1">
<FontIcon Margin="2,0" Glyph="" />
<TextBlock Margin="2,0" Text="{Binding Path=Score, Mode=OneWay}" />
<Button Content="Reply" Margin="2,0" />
</StackPanel>
</Grid>
<ItemsControl ItemTemplate="{RelativeSource Mode=Self}" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Margin="48" ItemsSource="{Binding Path=Comments}" />
</StackPanel>
</DataTemplate>
I'd like to self-reference the DataTemplate in the ItemsControl's ItemTemplate property. How would I go about replacing it?
Here is something that may work for you. I use this in our POS application - although this is my first attempt at nesting it. The xaml designer complains because of the selector StaticResource reference prior to its declaration, but it works at runtime.
Essentially, I use this to select a different data template based on the class that is bound to the item. In this case, there is only one type, so really we are just using the class type to decide what template to use.
MyModel:
public class MyModel
{
public int Id { get; set; }
public string Desc { get; set; }
public List<MyModel> Children { get; set; }
}
MyTemplateSelector:
public class MyTemplateSelector : DataTemplateSelector
{
public DataTemplate MyModelTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item)
{
if (item is MyModel)
{
return MyModelTemplate;
}
else
{
return null;
}
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
return SelectTemplateCore(item);
}
}
My MainPage:
<Page
x:Class="App7.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App7"
xmlns:models="using:App7.Models"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<DataTemplate x:Key="myModelTemplate" x:DataType="models:MyModel">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{x:Bind Path=Desc, Mode=OneWay}" />
<ListView Grid.Row="1" ItemTemplateSelector="{StaticResource selector}" ItemsSource="{x:Bind Path=Children, Mode=OneWay}" />
</Grid>
</DataTemplate>
<local:MyTemplateSelector x:Key="selector" MyModelTemplate="{StaticResource myModelTemplate}" />
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView x:Name="lstView" ItemTemplateSelector="{StaticResource selector}" >
</ListView>
</Grid>
</Page>
My code behind:
public sealed partial class MainPage : Page
{
List<MyModel> lst { get; set; }
public MainPage()
{
this.InitializeComponent();
BuildList();
lstView.ItemsSource = lst;
}
private void BuildList()
{
lst = new List<MyModel>();
for (int i = 0; i < 10; i++)
{
MyModel mod = new MyModel();
mod.Id = i;
mod.Desc = "Desc" + i.ToString();
mod.Children = new List<MyModel>();
for (int j = 100; j < 102; j++)
{
MyModel mod2 = new MyModel();
mod2.Id = j;
mod2.Desc = "Desc" + j.ToString();
mod2.Children = new List<MyModel>();
for (int k = 1000; k < 1002; k++)
{
MyModel mod3 = new MyModel();
mod3.Id = k;
mod3.Desc = "Desc" + k.ToString();
mod3.Children = new List<MyModel>();
mod2.Children.Add(mod3);
}
mod.Children.Add(mod2);
}
lst.Add(mod);
}
}
}

MVVM WCF Entity as Model... How to implement propertychange notification in the view model when using Entity as your model

I am implementing an MVVM Pattern using Entity as my model... When I instantiate the Model in the setter I add a propertychanged handler to the object to capture the field changes within the entity object...
When I step through code I find that after I modify the field within the object it is firing the Property Change event multiple times...
It has led me to believe that I have done something wrong...
I want to use the entity as my model as I do not want to recreate the object as an abstacted object...
Can any find any fault in the code base below that would cause the SelectedCompany.PropertyChanged to fire more then once when the datagrid field is modified...
I was expecting that that the property changed event would only fire once and am a bit perplexed as to why it fires multiple times...
Was it wrong to add the propertyChange event in the Setter...
Here is the ViewModel...
public class CompanyMaintenanceVM : INotifyPropertyChanged
{
private ServiceHandler _serviceHandler = new ServiceHandler();
private bool _ignorePropertyChange = false; //by default we will execute on every property change...
public CompanyMaintenanceVM()
{
_companyList = new ObservableCollection<Company>(_serviceHandler.GetCompanies().ToList());
_selectedCompany = _companyList.First();
UpdateCommand = new RelayCommand(Update) { IsEnabled = true };
SearchCommand = new RelayCommand(Search) { IsEnabled = true };
_companyTypeList = new ObservableCollection<CompanyType>(_serviceHandler.GetCompanyTypes().ToList());
}
private string _selectedKey;
public string SelectedKey
{
get { return _selectedKey; }
set
{
if (_selectedKey != value)
{
_selectedKey = value;
OnPropertyChanged("SelectedKey");
}
}
}
private Company _selectedCompany = new Company();
public Company SelectedCompany
{
get { return _selectedCompany; }
set
{
if (_selectedCompany != value)
{
_selectedCompany = value;
OnPropertyChanged("SelectedCompany");
SelectedCompany.PropertyChanged += new PropertyChangedEventHandler(SelectedCompany_PropertyChanged);
}
}
}
private ObservableCollection<Company> _companyList;
public ObservableCollection<Company> CompanyList
{
get { return _companyList; }
set
{
_companyList = value;
OnPropertyChanged("CompanyList");
}
}
private ObservableCollection<CompanyType> _companyTypeList;
public ObservableCollection<CompanyType> CompanyTypeList
{
get { return _companyTypeList; }
set { _companyTypeList = value; }
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
switch (propertyName)
{
case "SelectedKey":
if (_ignorePropertyChange == false)
{
SelectedKeyChanged();
}
_ignorePropertyChange = false;
break;
case "SelectedCompany":
_ignorePropertyChange = true;
SelectedKey = SelectedCompany.CompanyID;
break;
}
}
void SelectedCompany_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
//
}
#endregion
private void SelectedKeyChanged()
{
var companies = _serviceHandler.GetCompanyByID(SelectedKey);
if (companies.FirstOrDefault() != null)
{
CompanyList = new ObservableCollection<Company>(companies);
SelectedCompany = CompanyList.First();
}
else
{
MessageBox.Show("No Company Record Exsists Matching CompanyID " + _selectedKey);
}
}
private ICommand _updateCommand;
public ICommand UpdateCommand
{
get
{
if (_updateCommand == null)
_updateCommand = new Updater();
return _updateCommand;
}
set
{
_updateCommand = value;
}
}
private class Updater : ICommand
{
#region ICommand Members
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(Object parameter)
{
}
#endregion
}
private void Update()
{
_serviceHandler.UpdateRepository(SelectedCompany);
_serviceHandler.CommitRepository();
}
private ICommand _searchCommand;
public ICommand SearchCommand
{
get
{
if (_searchCommand == null)
_searchCommand = new Searcher();
return _searchCommand;
}
set
{
_searchCommand = value;
}
}
private class Searcher : ICommand
{
#region ICommand Members
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(Object parameter)
{
}
#endregion
}
private void Search()
{
}
}
Here is the XAML...
<Window x:Class="XERP.Client.WPF.CompanyMaintenance.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Company Maintenance" Height="800" Width="600">
<Grid>
<Grid.Resources>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="25"></RowDefinition>
<RowDefinition Height="422*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="156*"></ColumnDefinition>
<ColumnDefinition Width="422*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock HorizontalAlignment="Center" Grid.Column="1" Text="Company Maintenance Form"
FontSize="13"
Margin="130,3,129,5"></TextBlock>
<Menu IsMainMenu="True" Grid.ColumnSpan="2" Margin="2,2,370,2" Width="180">
<MenuItem Header="_File" >
<MenuItem Header="_New" Command="New">
</MenuItem>
<MenuItem Header="_Save"
IsCheckable="True"
Command="{Binding UpdateCommand}" Click="SaveMenuItem_Click">
</MenuItem>
<MenuItem Header="_Exit" Command="Close">
</MenuItem>
</MenuItem>
<MenuItem Header="_Edit">
<MenuItem Header="_Cut" Command="Cut">
</MenuItem>
<MenuItem Header="_Copy" Command="Copy">
</MenuItem>
<MenuItem Header="_Paste" Command="Paste">
</MenuItem>
</MenuItem>
<MenuItem Header="_Tools" />
<MenuItem Header="_Actions" />
<MenuItem Header="_Help" />
</Menu>
<GridSplitter Grid.Row="1" Grid.Column="0" HorizontalAlignment="Right" Width="4" Background="Yellow"/>
<ListView Grid.Row="1" Grid.Column="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="5"
ItemsSource="{Binding CompanyList}"
SelectedItem="{Binding SelectedCompany, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectionMode="Single"
IsSynchronizedWithCurrentItem="True"
>
<ListView.ItemTemplate>
<DataTemplate >
<TextBlock Text="{Binding Path=CompanyID, Mode=TwoWay}" Margin="5"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<TabControl Grid.Row="1" Grid.Column="1" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TabItem Header="Detail">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="46"></RowDefinition>
<RowDefinition Height="657*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="165*"></ColumnDefinition>
<ColumnDefinition Width="246*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Orientation="Horizontal" Grid.Row="0" Grid.ColumnSpan="2">
<Button Width="100" Height="20" Margin="10"
Command="{Binding SearchCommand}">Company...</Button>
<TextBox Width="100" Height="20" Margin=" 10"
Text="{Binding Path=SelectedKey, Mode=TwoWay}"
/>
</StackPanel>
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Orientation="Vertical" Grid.Row="1" Grid.Column="0">
<TextBlock HorizontalAlignment="Right" VerticalAlignment="Top" Margin="8">Name:</TextBlock>
<TextBlock HorizontalAlignment="Right" VerticalAlignment="Top" Margin="8">Description:</TextBlock>
<TextBlock HorizontalAlignment="Right" VerticalAlignment="Top" Margin="8">Type:</TextBlock>
</StackPanel>
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Orientation="Vertical" Grid.Row="1" Grid.Column="1">
<TextBox HorizontalAlignment="Left" VerticalAlignment="Top" Margin="5" Width="200"
Text="{Binding Path=SelectedCompany.Name, Mode=TwoWay}"
/>
<TextBox
HorizontalAlignment="Left" VerticalAlignment="Top" Margin="5" Width="200"
Text="{Binding Path=SelectedCompany.Description, Mode=TwoWay}"
/>
<ComboBox HorizontalAlignment="Left" Width="200" Margin="5"
ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.CompanyTypeList, Mode=TwoWay}"
DisplayMemberPath="Type"
SelectedValuePath="CompanyTypeID"
SelectedValue="{Binding Path=SelectedCompany.CompanyTypeID, Mode=TwoWay}"/>
<TextBox Height="23" Name="ghost" Width="0" />
</StackPanel>
</Grid>
</TabItem>
<TabItem Header="List" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid>
<DataGrid AutoGenerateColumns="False"
ItemsSource="{Binding CompanyList}"
SelectedItem="{Binding SelectedCompany, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectionMode="Single"
IsSynchronizedWithCurrentItem="True"
>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding CompanyID, Mode=TwoWay}"
Header="ID" Width="auto" IsReadOnly="True"/>
<DataGridTextColumn Binding="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
Header="Name" Width="Auto"/>
<DataGridTextColumn Binding="{Binding Description, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
Header="Description" Width="Auto"/>
<DataGridComboBoxColumn Header="Type" Width="Auto"
SelectedValueBinding ="{Binding CompanyTypeID, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
DisplayMemberPath="Type"
SelectedValuePath="CompanyTypeID">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.CompanyTypeList, Mode=TwoWay}"/>
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.CompanyTypeList, Mode=TwoWay}"/>
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
<TextBox Height="23" Name="ghost2" Width="0" />
</Grid>
</TabItem>
</TabControl>
</Grid>
</Window>
be aware of using selecteditem with twoway binding AND IsSynchronizedWithCurrentItem="True" - maybe there you get your roundtrips.
and what your cause for the following?
if (_selectedCompany != value)
{
_selectedCompany = value;
OnPropertyChanged("SelectedCompany");
SelectedCompany.PropertyChanged += new PropertyChangedEventHandler(SelectedCompany_PropertyChanged); //<-- whats that?
}
and why you do this?
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
switch (propertyName)//why you do this?
{
case "SelectedKey":
if (_ignorePropertyChange == false)
{
SelectedKeyChanged();
}
_ignorePropertyChange = false;
break;
case "SelectedCompany":
_ignorePropertyChange = true;
SelectedKey = SelectedCompany.CompanyID;
break;
}
}