I'm trying to close a flyout on a listview item click. The problem is that during runtime, the CallMethodAction can't find the hide method of the flyout menu. How can I fix this?
<Flyout x:Name="UnitFlyout">
<ListView x:Name="ArmyUnitListView" ItemsSource="{Binding Source={StaticResource ArmyUnitCollection}}" SelectionMode="Single" >
<ListView.GroupStyle>
<GroupStyle HidesIfEmpty="True" HeaderTemplate="{StaticResource ArmyListDataGroupTemplate}" />
</ListView.GroupStyle>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="SelectionChanged">
<core:InvokeCommandAction Command="{Binding AddUnitCommand}" CommandParameter="{Binding SelectedItem, ElementName=ArmyUnitListView}" />
<core:CallMethodAction TargetObject="UnitFlyout" MethodName="Hide"/>
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</ListView>
</Flyout>
I made a demo and reproduced the problem.The reason lies on the ElementName binding to popups.
ElementName bindings do not work within Flyout’s and other popups
Please see this Case.
I found this blog, which offers a workaround to fix this problem. And I've tried it with a demo,which works fine.
In your case, you can copy the FlyoutHelpers (in blog) class to your project; And add IsFlyoutOpen and SendCommand to your ViewModel like below:
public class MainPageViewModel : ViewModelBase
{
public RelayCommand SendCommand { get; set; }// bind this to your xaml
public List<String> MyData { get; set; }
private bool isFlyoutOpen;
public bool IsFlyoutOpen// bind this to your xaml
{
get { return isFlyoutOpen; }
set { this.Set(() => IsFlyoutOpen, ref isFlyoutOpen, value); }
}
public MainPageViewModel()
{
SendCommand = new RelayCommand(() =>
{
// Doing processing...
IsFlyoutOpen = false;
});
MyData = new List<string> { "winffee", "123", "this Data" };//this is sample data
}
}
And Bind the commands and properties to your xaml:
<Flyout x:Name="UnitFlyout"
local:FlyoutHelpers.Parent="{Binding ElementName=myBtn}"
local:FlyoutHelpers.IsOpen="{Binding IsFlyoutOpen,Mode=TwoWay}">
<ListView x:Name="ArmyUnitListView" SelectionMode="Single" ItemsSource="{Binding MyData}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="SelectionChanged" SourceObject="{Binding ElementName=ArmyUnitListView}">
<!--<core:InvokeCommandAction Command="{Binding AddUnitCommand}" CommandParameter="{Binding SelectedItem, ElementName=ArmyUnitListView}" />-->
<core:InvokeCommandAction Command="{Binding SendCommand}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</ListView>
</Flyout>
Here is my entire Demo: FlyoutSample
Related
How do I get a datagrid like this with templates for rows as well as for repeatable cells?
DataGrid is bound to a property called Blocks, which is a BindingList of Block type, defined in the view model:
<DataGrid x:Name="dgBlocks" DockPanel.Dock="Left"
Margin="20,10,10,20" AutoGenerateColumns="False"
ItemsSource="{Binding Blocks, Mode=TwoWay}"
IsSynchronizedWithCurrentItem="True"
CanUserAddRows="False"
SelectionUnit="Cell" SelectionMode="Single"
EnableColumnVirtualization="False"
EnableRowVirtualization="False"
SelectedIndex="-1" MinRowHeight="10"
LoadingRow="dg_LoadingRow"
SelectionChanged="dgBlocks_SelectionChanged"
>
<DataGrid.Resources>
<helper:BindingProxy x:Key="proxy" Data="{Binding}" />
<!--todo: change to symbol’s ‘ReadOnly’ property instead of block’s ‘Permission’ -->
<Style x:Key="CellPermissionStyle" TargetType="DataGridCell">
<Setter Property="Background" Value="{Binding Permission, Converter={StaticResource BlockPermissionToBrushConverter}}" />
<Setter Property="IsEnabled" Value="{Binding Permission, Converter={StaticResource BlockPermissionToBoolConverter}}" />
</Style>
</DataGrid.Resources>
<DataGrid.RowHeaderTemplate>
<DataTemplate>
<TextBlock Name="txtBlockRowHeader"
Text="{Binding Path=Header,
RelativeSource={RelativeSource AncestorType=DataGridRow}}">
</TextBlock>
</DataTemplate>
</DataGrid.RowHeaderTemplate>
<DataGrid.Columns>
<DataGridTemplateColumn>
<!-- Selection checkboxes -->
<DataGridTemplateColumn.Header>
<StackPanel>
<CheckBox HorizontalAlignment="Center" IsThreeState="True" ToolTip="Select All" Margin="10,0,0,0">
<CheckBox.IsChecked>
<Binding Path="DataContext.SelectAll"
RelativeSource="{RelativeSource AncestorType={x:Type UserControl}}"
Mode="TwoWay"
UpdateSourceTrigger="PropertyChanged"
/>
</CheckBox.IsChecked>
</CheckBox>
</StackPanel>
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox Margin="5,0,0,0" IsChecked="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="50">
<DataGridTemplateColumn.Header>
<TextBlock Text="Repeat" HorizontalAlignment="Center" />
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<telerik:RadNumericUpDown Name="nudRepeatBlock" ValueFormat="Numeric" Width="40"
ToolTip="Repeat block number of times" IsInteger="True" IsEditable="True"
Minimum="1" Maximum="100" UpdateValueEvent="PropertyChanged"
Value="{Binding Repeat, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" MinWidth="40" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" IsReadOnly="True" CanUserReorder="False" CanUserResize="False"
Visibility="{Binding Data.Encoding, Converter={StaticResource Encoding8b10bToCollapsedConverter}, Source={StaticResource proxy}}">
<DataGridTextColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
</DataGridTextColumn.HeaderStyle>
</DataGridTextColumn>
<!--todo: change… -->
<DataGridTextColumn Header="[0]" Binding="{Binding .Symbols[0].SymbolText, UpdateSourceTrigger=LostFocus}" CellStyle="{StaticResource CellPermissionStyle}" />
<DataGridTextColumn Header="[1]" Binding="{Binding .Symbols[1].SymbolText, UpdateSourceTrigger=LostFocus}" CellStyle="{StaticResource CellPermissionStyle}" />
<!-- … -->
<DataGridTextColumn Header="[15]" Binding="{Binding .Symbols[15].SymbolText, UpdateSourceTrigger=LostFocus}" CellStyle="{StaticResource CellPermissionStyle}" />
</DataGrid.Columns>
</DataGrid>
Each Block object has a property called Symbols, defined as a BindingList of Symbol type, and some other properties. Symbols get shown in columns with headers [0], 1, etc.
Other block’s properties shown in other columns. E.g. IsSelected for checkbox, Reapeat, Name.
public interface ISymbol
{
int Index { get; set; }
bool ReadOnly { get; set; }
string SymbolText { get; set; }
}
public class BlockBase : ObservableObject, IDataErrorInfo
{
public bool IsSelected
{
get { return _isSelected; }
set
{
if (value == _isSelected)
return;
_isSelected = value;
OnPropertyChanged("IsSelected");
RaiseSelectionChangedEvent();
}
}
public string Name { get; set; }
public virtual BindingList<ISymbol> Symbols { get; set; }
public BlockPermission Permission { get; set; }
public int Repeat { get; set; }
}
public class Symbol : ObservableObject, ISymbol
{
DisplayFormat Format { get; set; }
public int Index { get; set; }
public virtual bool ReadOnly { get; set; }
public virtual string SymbolText
{
get { return (Format == DisplayFormat.Binary) ? _binSymbol : _hexSymbol;
}
set
{
// … validate and set _binSymbol & _hexSymbol values
OnPropertyChanged("SymbolText");
}
}
Now, I need to bind each symbol’s cell 'IsEnabled' property to Symbol's 'ReadOnly' property.
I tried to define this in DataGrid.Resources as CellPermissionStyle, but I don’t know how to access a Symbol on this level.
Also, it would be nice to replace all repeating DataGridTextColumn defenitions for symbols with a template.
Would somebody help me?
My researches just confirmed that unfortunately there is no way to do this with templates or styles applicable to a subset of columns. It is also imposible to send a parameter to a style or template as Mishka suggested :(.
So, I've just ended up with 'copy-paste-modify' cell style for each of my 16 columns like that:
<DataGridTextColumn Header="[0]" Binding="{Binding .Symbols[0].SymbolText, UpdateSourceTrigger=LostFocus}">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="Background" Value="{Binding .Symbols[0].ReadOnly, Converter={StaticResource SymbolReadOnlyToBrushConverter}}" />
<Setter Property="IsEnabled" Value="{Binding .Symbols[0].ReadOnly, Converter={StaticResource SymbolReadOnlyToEnabledConverter}}" />
<Setter Property="TextBlock.TextAlignment" Value="Center" />
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
...
<DataGridTextColumn Header="[15]" Binding="{Binding .Symbols[15].SymbolText, UpdateSourceTrigger=LostFocus}">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="Background" Value="{Binding .Symbols[15].ReadOnly, Converter={StaticResource SymbolReadOnlyToBrushConverter}}" />
<Setter Property="IsEnabled" Value="{Binding .Symbols[15].ReadOnly, Converter={StaticResource SymbolReadOnlyToEnabledConverter}}" />
<Setter Property="TextBlock.TextAlignment" Value="Center" />
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
Yes, I know, this XAML looks ugly, but it works for me without a risky redesign of existing code base.
I'd be happy if someone could suggest a better, more elegant way doing this in XAML.
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>
I have the following, very simple repro case. It's a Universal Windows (Phone) 8.1 app. I have a very simple model:
public class SimpleModel
{
public bool IsLoading { get; set; } = false;
}
and a collection of these models, defined as shared classes:
public class SimpleModelCollection : List<SimpleModel>
{
public SimpleModelCollection()
{
this.Add(new SimpleModel());
this.Add(new SimpleModel());
this.Add(new SimpleModel());
this.Add(new SimpleModel());
this.Add(new SimpleModel());
this.Add(new SimpleModel());
}
}
I have only a single page in both Windows and Windows Phone 8.1 project. They have identical XAML, a simple ItemsControl:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ItemsControl x:Name="items" HorizontalAlignment="Center">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<interactivity:Interaction.Behaviors>
<core:DataTriggerBehavior Binding="{Binding IsLoading}" Value="True">
<core:ChangePropertyAction TargetObject="{Binding ElementName=text}" PropertyName="Visibility" Value="Visible"/>
</core:DataTriggerBehavior>
<core:DataTriggerBehavior Binding="{Binding IsLoading}" Value="False">
<core:ChangePropertyAction TargetObject="{Binding ElementName=text}" PropertyName="Visibility" Value="Collapsed"/>
</core:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
<StackPanel>
<TextBlock x:Name="text" Text="Should I be visible?" FontSize="26"></TextBlock>
</StackPanel>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
Of course, Behaviors SDK is added to both (Win & WP 8.1) projects.
In code-behind, I set the ItemsSource to an instance of the previously mentioned simple model collection. Windows Phone constructor:
public MainPage()
{
this.InitializeComponent();
this.items.ItemsSource = new SimpleModelCollection();
this.NavigationCacheMode = NavigationCacheMode.Required;
}
and Windows constructor:
public MainPage()
{
this.InitializeComponent();
this.items.ItemsSource = new SimpleModelCollection();
}
What would you expect as a result if IsLoading was initialized to false:
public bool IsLoading { get; set; } = false;
Let me show you what the app looks like if Isloading is initialized to false on Windows Phone:
This is OK, and completely expected, since the Visibility maps to the bool value, so the TextBlocks should be collapsed if IsLoading is false. But on Windows, they are not:
My question is - why? What am I missing?
This is also problematic when comparing WP 8.1 behavior with the behavior in Windows 10 UWP. In UWP it behaves just like on Windows 8.1, which make porting from WP 8.1 to UWP a bit painful.
EDIT: The full repro project is here: https://github.com/igrali/BehaviorsSDK_Bug
This is indeed a bug. I can see two ways to fix it.
First, if you absolutely know that the initial state of the TextBlocks should be Collapsed, you can default them to Collapsed in XAML as it only doesn't work for the first time.
Alternatively, you can attach all the Behaviors to the Textblock directly inside the template. This should work too.
<DataTemplate>
<Grid>
<StackPanel>
<TextBlock x:Name="text" Text="Should I be visible?" FontSize="26">
<interactivity:Interaction.Behaviors>
<core:DataTriggerBehavior Binding="{Binding IsLoading}" Value="True">
<core:ChangePropertyAction TargetObject="{Binding ElementName=text}" PropertyName="Visibility" Value="Visible" />
</core:DataTriggerBehavior>
<core:DataTriggerBehavior Binding="{Binding IsLoading}" Value="False">
<core:ChangePropertyAction TargetObject="{Binding ElementName=text}" PropertyName="Visibility" Value="Collapsed" />
</core:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</TextBlock>
<Button Click="Button_Click" Content="Visible?" />
</StackPanel>
</Grid>
</DataTemplate>
I'm building a Windows Store app with C#/XAML.
I have a simple ListView bound to an ItemsSource. There's a DataTemplate which defines the structure of each item and that has a ContentControl and a TextBlock in it.
I wish to change the Foreground colour of the TextBlock when the item is selected. Does anyone know how I can do this?
<ListView Grid.Column="1"
ItemsSource="{Binding Categories}"
ItemContainerStyle="{StaticResource CategoryListViewItemStyle}"
Background="{StaticResource DeepRedBrush}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ContentControl Content="{Binding Id, Converter={StaticResource Cat2Icon}}" HorizontalAlignment="Left" VerticalAlignment="Center" Width="110" Foreground="#FF29BCD6"/>
<TextBlock x:Name="catName" HorizontalAlignment="Left" Margin="0" TextWrapping="Wrap" Text="{Binding Name}" Grid.Column="1" VerticalAlignment="Center" FontSize="18.667"
Foreground="White"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
At the moment it's set to "White", so all I need is some binding expression that will change the Foreground property depending on the selected state of the item in the listview.
This does what you are asking for.
Using this XAML
<Grid x:Name="LayoutRoot" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView x:Name="MyListView" ItemsSource="{Binding Items}" SelectionMode="Single" SelectedItem="{Binding Selected, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid Height="100" Width="300">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Ellipse x:Name="ellipse">
<Ellipse.Fill>
<SolidColorBrush Color="{Binding Color}" />
</Ellipse.Fill>
</Ellipse>
<TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="10" Text="{Binding Title}" Style="{StaticResource HeaderTextBlockStyle}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
And this code behind:
public class MyModel : BindableBase
{
string _Title = default(string);
public string Title { get { return _Title; } set { SetProperty(ref _Title, value); } }
Color _Color = Colors.White;
public Color Color { get { return _Color; } set { SetProperty(ref _Color, value); } }
}
public class MyViewModel : BindableBase
{
public MyViewModel()
{
var items = Enumerable.Range(1, 10)
.Select(x => new MyModel { Title = "Title " + x.ToString() });
foreach (var item in items)
this.Items.Add(item);
}
MyModel _Selected = default(MyModel);
public MyModel Selected
{
get { return _Selected; }
set
{
if (this.Selected != null)
this.Selected.Color = Colors.White;
SetProperty(ref _Selected, value);
value.Color = Colors.Red;
}
}
ObservableCollection<MyModel> _Items = new ObservableCollection<MyModel>();
public ObservableCollection<MyModel> Items { get { return _Items; } }
}
public abstract class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void SetProperty<T>(ref T storage, T value, [System.Runtime.CompilerServices.CallerMemberName] String propertyName = null)
{
if (!object.Equals(storage, value))
{
storage = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
protected void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] String propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
It will update your data template for you.
I want to make this quick point: updating the content of your list through the ViewModel is the easiest and most light-weight approach. In this case, I am updating the color which is bound to the ellipse. However, if this were a complex set of changes, I might just set a style instead. Another option is to hide and show an entire set of controls in the template. You cannot, however, change the data template because it will not be re-rendered until the grid re-draws, and that's not what you want to do.
Just like changing the Ellipse color, you could change the TextBlock Foreground like you asked in your question. Either way, this gets you what you want in the most elegant way.
Best of luck!
You can simply handle the SelectionChanged event on the ListView and change the Foreground of the previously selected item and the newly selected item by either changing a view model value on SelectedItem that is bound to your Foreground.
You can also find the TextBlock using ListView.ItemContainerGenerator.ContainerFromItem(ListView.SelectedItem) + VisualTreeHelper as in Jerry Nixon's blog post and changing the Foreground directly, though that technique has a problem if your ListView is virtualized (which it is by default), since if you scroll away from the selected item and back - the item view with the changed Foreground might be recycled and used for another item in your collection.
Another option is to bind the Foreground to the IsSelected property of the parent ListViewItem which you can do in many ways as well. You could for example put your entire DataTemplate in a UserControl and bind the Foreground to the Parent of that control. The problem is I think Parent is not a dependency property and I see no ParentChanged event on FrameworkElement (base class for UserControl that defines the Parent property), so it might be tough to go this route. Another way to bind these is to define an attached dependency property or behavior that would set up that binding for you, but that is complicated (though I have already created one you could use here).
Finally you could modify your ListView.ItemContainerStyle and change the SelectedBackground value. If that works - it would be the ideal solution.
<Style TargetType="ListViewItem">
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}"/>
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="TabNavigation" Value="Local"/>
<Setter Property="IsHoldingEnabled" Value="True"/>
<Setter Property="Margin" Value="0,0,18,2"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="VerticalContentAlignment" Value="Top"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<ListViewItemPresenter CheckHintBrush="{ThemeResource ListViewItemCheckHintThemeBrush}" CheckBrush="{ThemeResource ListViewItemCheckThemeBrush}" ContentMargin="4" ContentTransitions="{TemplateBinding ContentTransitions}" CheckSelectingBrush="{ThemeResource ListViewItemCheckSelectingThemeBrush}" DragForeground="{ThemeResource ListViewItemDragForegroundThemeBrush}" DragOpacity="{ThemeResource ListViewItemDragThemeOpacity}" DragBackground="{ThemeResource ListViewItemDragBackgroundThemeBrush}" DisabledOpacity="{ThemeResource ListViewItemDisabledThemeOpacity}" FocusBorderBrush="{ThemeResource ListViewItemFocusBorderThemeBrush}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" Padding="{TemplateBinding Padding}" PointerOverBackgroundMargin="1" PlaceholderBackground="{ThemeResource ListViewItemPlaceholderBackgroundThemeBrush}" PointerOverBackground="{ThemeResource ListViewItemPointerOverBackgroundThemeBrush}" ReorderHintOffset="{ThemeResource ListViewItemReorderHintThemeOffset}" SelectedPointerOverBorderBrush="{ThemeResource ListViewItemSelectedPointerOverBorderThemeBrush}" SelectionCheckMarkVisualEnabled="True" SelectedForeground="{ThemeResource ListViewItemSelectedForegroundThemeBrush}" SelectedPointerOverBackground="{ThemeResource ListViewItemSelectedPointerOverBackgroundThemeBrush}" SelectedBorderThickness="{ThemeResource ListViewItemCompactSelectedBorderThemeThickness}" SelectedBackground="{ThemeResource ListViewItemSelectedBackgroundThemeBrush}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Is there a way to define a GridView purely declaratively in XAML? Every example I see defines the Category/Group Name value in code behind, then each item has a Title / Subtitle. I would really like to figure out a way to do something like the following:
<GridView
x:Name="itemGridView"
AutomationProperties.AutomationId="ItemGridView"
AutomationProperties.Name="Grouped Items"
Grid.Row="1"
Margin="0,-3,0,0"
Padding="116,0,40,46"
ItemTemplate="{StaticResource Standard250x250ItemTemplate}">
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
<GridView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Grid Margin="1,0,0,6">
<Button
AutomationProperties.Name="Group Title"
Content="{Binding Tag}"
Style="{StaticResource TextButtonStyle}"/>
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.Panel>
<ItemsPanelTemplate>
<VariableSizedWrapGrid Orientation="Vertical" Margin="0,0,80,0"/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</GridView.GroupStyle>
<GridView.Items>
<Grid x:Name="tab1" Tag="Tab 1" AutomationProperties.Name="Group Title" HorizontalAlignment="Left" Width="250" Height="250">
<!-- Tab COntent -->
</Grid>
<Grid x:Name="tab2" Tag="Tab 2" AutomationProperties.Name="Tab 2" HorizontalAlignment="Left" Width="250" Height="250">
<!-- Tab Content -->
</Grid>
</GridView.Items>
</GridView>
Unfortunately this example doesn't work. I can't seem to figure out how to set a Group name declaratively. Thank you for any insights.
I think the following method is great way to customize any Gridview.
*If you like to implement multiple group style in your Grid, pls do this work:
Step 1. Create a GroupSelector class and override SelectStyleCore function:
public class GroupStyleSelector : StyleSelector
{
public Style GroupStyle1 { get; set; }
public Style GroupStyle2 { get; set; }
protected override Windows.UI.Xaml.Style SelectStyleCore(object item, Windows.UI.Xaml.DependencyObject container)
{
var viewGroup = item as ICollectionViewGroup;
if (viewGroup != null)
{
//var groupModel = viewGroup.Group as YourGroupModel;
if (condition1)
{
return GroupStyle1;
}
else if (condition2)
{
return GroupStyle2;
}
}
}
}
Step 2. in XAML resource, add these code:
<Style x:Key="YourGroupStyle1" TargetType="GroupItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GroupItem">
<!-- Add your custom layout here for Group style 1 -->
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="YourGroupStyle2" TargetType="GroupItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GroupItem">
<!-- Add your custom layout here for Group style 1 -->
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<customstyle:GroupStyleSelector x:Key="GroupStyleSelector"
GroupStyle1="{StaticResource GroupStyle1}"
GroupStyle2="{StaticResource GroupStyle2}"/>
customstyle is link to your namespace that store GroupStyleSelector class.
Step 3. Apply style selector for GridView control
If you you want to customize HeaderTemplate pls let me know, if not user current as GridApp sample from Microsoft.
If you like to customize each item layout on a group, you also do simiar to GroupStyleSelector:
Create DataTemplateSeletor (inherit from TemplateSelector) then assign it on the GridView control
<GridView ItemTemplateSelector="{StaticResource ItemTemplateSelector}">
<GridView.GroupStyle>
<GroupStyle ContainerStyleSelector="{StaticResource GroupStyleSelector}">
<GroupStyle.HeaderTemplate .../>
</GridView.GroupStyle>
Hope that my guide is clear for you.
If any quesion, pls discuss.