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);
}
}
}
Related
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.
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>
Well, I want to make the ListViewItem in UWP ,that'll be changing his view on selecting. So I need to change the Visibility property of some elements of ListViewItem on selecting.
I found some way to do this with making custom Style of ListViewItem and Binding IsSelected property like this:
<Style x:Key="VehicleListViewItemStyle" TargetType="ListViewItem" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<Grid Background="Gray" Margin="1">
<Border Margin="2" Padding="10" Background="Gray" >
<StackPanel>
<ContentPresenter x:Name="Presenter1" />
<StackPanel Orientation="Horizontal" Background="Transparent" Margin="-10,0,-9,-9" VerticalAlignment="Center" x:Name="infoPanel"
Visibility="{Binding IsSelected, RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource BooleanToVisibilityConverter}}">
<TextBlock Text="{Binding DeviceID}/> </StackPanel>
</StackPanel>
</Border>
<Border BorderThickness="1" BorderBrush="Orange" Visibility="{Binding IsSelected, RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource BooleanToVisibilityConverter}}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Its working good, but with this way I cant bind the DeviceID text.
Another one way is creating DataTemplate like this:
<DataTemplate x:Key="monitoringListViewItem" x:Name="item">
<Grid Background="Gray" Margin="1" Width="300" >
<StackPanel>
<ContentPresenter x:Name="Presenter"/>
<StackPanel Orientation="Horizontal">
<Image Source="/Assets/14th_crane_stop.png" Height="50" Width="50" Stretch="Uniform"/>
<StackPanel Orientation="Vertical" Margin="25,0,0,0 "
Visibility="{Binding IsSelected, RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource BooleanToVisibilityConverter}}"
>
<TextBlock Text="{Binding DeviceID}" Style="{StaticResource VehicleTextStyle}"/>
<TextBlock Text="{Binding Mark}" Style="{StaticResource VehicleTextStyle}"/>
</StackPanel>
</StackPanel>
</StackPanel >
</Grid>
</DataTemplate>
Now I can bind the text correctly, but cant bind the IsSelected property. I've tried to do this with different Modes, but it still doesn't work because I cant use the TemplatedParent key inside the DataTemplate.
So I need some answers:
-can I bind the text in first way and how can I do that?
-how can I bind the IsSelected property in the second way?
I don't recommend changing the ListViewItem template since you lose all the bells and whistles it provides (selection appearance, ability to be checked, etc).
Using Mode=TemplatedParent in the second snippet won't work because the templated parent from that context is a ListViewItemPresenter, not the ListViewItem (which is the presenter's parent).
It looks like what you're trying to do is to show additional information in the list item when it is selected.
Single selection
C# classes
public class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetProperty<T>(ref T property, T value, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(property, value))
{
return false;
}
property = value;
OnPropertyChanged(propertyName);
return true;
}
}
public class Item : NotifyPropertyChangedBase
{
private string text;
public string Text
{
get { return text; }
set { SetProperty(ref text, value); }
}
private bool isSelected;
public bool IsSelected
{
get { return isSelected; }
set { SetProperty(ref isSelected, value); }
}
}
public class MainPageViewModel : NotifyPropertyChangedBase
{
public List<Item> Items { get; set; }
private Item selectedItem;
public Item SelectedItem
{
get { return selectedItem; }
set
{
if (selectedItem != value)
{
if (selectedItem != null)
{
selectedItem.IsSelected = false;
}
SetProperty(ref selectedItem, value);
if (selectedItem != null)
{
selectedItem.IsSelected = true;
}
}
}
}
public MainPageViewModel()
{
Items = new List<Item>()
{
new Item() { Text = "Apple" },
new Item() { Text = "Banana" },
};
}
}
MainPage.xaml
<ListView ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Item">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Text}"/>
<!-- x:Bind doesn't require visibility converter if min SDK is targeting Anniversary update -->
<TextBlock Text="I'm selected!" Grid.Column="1" Visibility="{x:Bind IsSelected, Mode=OneWay}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
MainPage.xaml.cs
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
DataContext = new MainPageViewModel();
}
}
Multiple selection
Same as single selection but with the following changes:
MainPageViewModel
public class MainPageViewModel : NotifyPropertyChangedBase
{
public List<Item> Items { get; set; }
public MainPageViewModel()
{
Items = new List<Item>()
{
new Item() { Text = "Apple" },
new Item() { Text = "Banana" },
};
}
public void SelectionChanged(object sender, SelectionChangedEventArgs e)
{
foreach (Item item in e.RemovedItems)
{
item.IsSelected = false;
}
foreach (Item item in e.AddedItems)
{
item.IsSelected = true;
}
}
}
MainPage.xaml
<ListView ItemsSource="{Binding Items}" SelectionChanged="{x:Bind ViewModel.SelectionChanged}" SelectionMode="Extended">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Item">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Text}"/>
<TextBlock Text="I'm selected!" Grid.Column="1" Visibility="{x:Bind IsSelected, Mode=OneWay}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
MainPage.xaml.cs
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
DataContext = new MainPageViewModel();
}
public MainPageViewModel ViewModel => (MainPageViewModel)DataContext;
}
To answer the actual question in the topic: Yes you can bind ListViewItem.IsSelected quite easily. Make sure your DataTemplate content is wrapped in a ListViewItem.
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:MyItemViewModel">
<ListViewItem IsSelected="{Binding IsSelected, Mode=TwoWay}">
<Grid>
// template content
</Grid>
</ListViewItem>
</DataTemplate>
</ListView.ItemTemplate>
Note that there are numerous Q&As here for WPF saying this does not work. It does work with UWP (at least as of SDK 10.0.19041). The WPF answers suggest binding it in the ItemsPanelTemplate or in a ResourceDictionary. For some reason this does not work in UWP.
I did the following and it worked fine
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:PersonalProfile">
<ListViewItem x:Name="root">
<Grid >
<!-- .... -->
<CheckBox IsChecked="{Binding ElementName=root, Path=IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</ListViewItem>
</DataTemplate>
</ListView.ItemTemplate>
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.
I am doing a Grid of two columns that are inside a ListBox. That after that I can DataBind the two columns to be repeated vertically.
So far the code below shows nothing on the WP7 emulator.
<ListBox Background="Yellow" ItemsSource="{Binding}" Height="100" Margin="0,0,8,0">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Height="100">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<TextBlock Text="Channels" HorizontalAlignment="Stretch" Foreground="Black" Grid.Column="0" />
<TextBlock Text="Antenna" HorizontalAlignment="Stretch" Foreground="Black" Grid.Column="1"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Please help me.
If your only concern is that you see ItemTemplate in action, you can supply explicit non-UI items as follows:
<ListBox Background="Yellow" Height="100" Margin="0,0,8,0" xmlns:sys="clr-namespace:System;assembly=mscorlib">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Height="30">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<TextBlock Text="Channels" HorizontalAlignment="Stretch" Foreground="Black" Grid.Column="0" />
<TextBlock Text="Antenna" HorizontalAlignment="Stretch" Foreground="Black" Grid.Column="1"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
<sys:String>1111111</sys:String>
<sys:String>2222222</sys:String>
<sys:String>3333333</sys:String>
</ListBox>
Notes:
I removed ItemsSource and supplied items explicitly.
Items must not derive from UIElement so that they are templated. (UIElements are simply drawn and the template is ignored.)
I added System namespace so that string objects can be specified.
I decreased ItemTemplate height so that more than one list row is visible.
Easier solution:
Give the ListBox a name and remove the binding:
<ListBox x:Name="myLB" Background="Yellow" Height="100" Margin="0,0,8,0">
Then use this line in the code (after the call InitializeComponent()):
myLB.ItemsSource = new List<string> { "First", "Second", "Third" };
If you want design-time itemssource, you can use the IsInDesignMode property like so:
if (System.ComponentModel.DesignerProperties.IsInDesignTool)
{
myListBox.ItemsSource = GenerateMockItems();
}
else
{
myListBox.ItemsSource = GetRealItems();
}
in MVVMLight ViewModels, this is shortcut-ed as
if (IsInDesignMode)
{
}
Similarly, since it looks like you're setting your ItemsSource in xaml, inside your class that is your DataContext, you could do something like
public class MyViewModel
{
public MyViewModel()
{
if (System.ComponentModel.DesignerProperties.IsInDesignTool)
{
Items = GenerateMockItems();
EditTime = GenerateRandomFutureDate();
}
else
{
//whatever you expect should happen in runtime
}
}
//what list is binding to
public ObservableCollection<Item> Items { get; set; }
//other properties.. for example
public bool HasItems { get { return Items != null && Items.Count > 0; } }
public DateTime EditDate { get; set; }
private ObservableCollection<Item> GenerateMockItems()
{
var collection = new ObservableCollection<Item>();
for (int i = 0; i < 10; i++)
{
collection.Add(new Item() { Name="sdfsdfs" , Channel=i });
}
return collection;
}
private DateTime GenerateRandomFutureDate()
{
return DateTime.Now.AddSeconds(new Random().Next(0,50000));
}
}