DataTemplate Binding Error - c#

I am having a problem trouble shooting a binding error in a datatemplate. I create an observable collection, and set mySelectFlag True for each filename. When the Datagrid displays, the checkbox on the header is checked, and each row in the datagrid has its checkbox checked. If I select/unselect a row checkbox, the change shows up on the observable collection. If I uncheck the header checkbox, I set each record on the observable collection to false. I call OnPropertyChanged on the observable collection, but the datagrid does not reflect the change. There are no binding errors. Can anyone tell me what I am doing wrong?
Thank you very much.
I have a class:
public class InputFileName
{
public bool mySelectFlag { get; set; }
public string myFileName { get; set; }
}
and a ObservableCollection:
private ObservableCollection<InputFileName> _DisplayList;
public ObservableCollection<InputFileName> DisplayList
{
get { return _DisplayList; }
set
{
if (value != _DisplayList)
{
_DisplayList = value;
OnPropertyChanged("DisplayList");
}
}
}
and this is my xaml:
<DataGrid
Margin="25"
RowHeaderWidth="0"
AutoGenerateColumns="False"
AlternatingRowBackground="Gainsboro"
AlternationCount="2"
Block.TextAlignment="Center"
CanUserAddRows="False"
CanUserReorderColumns="False"
CanUserResizeColumns="False"
CanUserSortColumns="False"
ItemsSource="{Binding Path=DisplayList}" >
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<CheckBox
IsChecked="{Binding Path=DataContext.IsSelected, ElementName=myControl1, FallbackValue=False}"
Command="{Binding Path=DataContext.SelectAllRows, ElementName=myControl1}"
Content="Select"
FontWeight="Bold"
Width="Auto" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGridTemplateColumn.HeaderStyle>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsChecked="{Binding Path=mySelectFlag, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, FallbackValue=False}"
Command="{Binding Path=DataContext.TestTaskCommand, ElementName=myControl1}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=SelectedIndex}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn
Header="File Name"
FontWeight="Bold"
Width="Auto"
Binding="{Binding Path=myFileName}" />
</DataGrid.Columns>
</DataGrid>

You need to implement INotifyPropertyChanged interface on your InputFileName class as weill to make changes visible to UI -
private bool mySelectFlag;
public bool MySelectFlag
{
get
{
return mySelectFlag;
}
set
{
mySelectFlag = value;
OnPropertyChanged("MySelectFlag");
}
}

INotifyPropertyChanged interfaceto be implemented by your class, a method should be defined like the one below and all the properties should call the OnPropertyChangeEvent
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string e)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(e));
}

Related

How to toggle visibility of a single DataGridRow in WPF?

Goal
I am aiming to create a button that triggers selected row RowDetailsTemplate visibility.
Problem
I somewhat managed to do it, but on my button click.. it displays RowDetailsTemplates for every single record. I need it to display the selected row, not all.
Collapsed
Visible
Question
How can I only trigger the selected row visibility state?
Code
XAML
<DataGrid ItemsSource="..." SelectedItem="..." IsReadOnly="True">
<DataGrid.Columns>
<DataGridTextColumn Header="Product Code" Binding="{Binding ProductCode}" />
<DataGridTemplateColumn Header="Actions">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Content="Edit" Command="{Binding RelativeSource={RelativeSource AncestorType=DataGrid}, Path=DataContext.TriggerVisibility }" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<StackPanel Background="Orange">
<TextBlock Text="Test" />
</StackPanel>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="DetailsVisibility" Value="{Binding RelativeSource={RelativeSource AncestorType=DataGrid}, Path=DataContext.IsVisible}" />
</Style>
</DataGrid.RowStyle>
</DataGrid>
View Model
public ICommand TriggerVisibility { get; }
private void GetVisibleCondition()
{
if(IsVisible == Visibility.Visible)
{
IsVisible = Visibility.Collapsed;
}
else if(IsVisible == Visibility.Collapsed)
{
IsVisible = Visibility.Visible;
}
}
private Visibility _isVisible = Visibility.Collapsed;
public Visibility IsVisible
{
get { return _isVisible; }
set
{
_isVisible = value;
OnPropertyChanged(nameof(IsVisible));
}
}
You cannot use a single property of a single view model to toggle the visibility of each individual row.
You should add an IsDetailsVisibile property at row level, i.e. to your data object T in the IEnumerable<T> ItemsSource of the DataGrid:
public class Product : INotifyPropertyChanged
{
public Product()
{
TriggerVisibility = new RelayCommand2(() => IsDetailsVisibile = !IsDetailsVisibile);
}
public string ProductCode { get; set; }
public ICommand TriggerVisibility { get; }
private bool _isDetailsVisibile;
public bool IsDetailsVisibile
{
get { return _isDetailsVisibile; }
set { _isDetailsVisibile = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "") =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public class RelayCommand : ICommand
{
private readonly Action _execute;
public RelayCommand(Action execute) => _execute = execute;
public event EventHandler CanExecuteChanged;
public bool CanExecute(Object parameter) => true;
public void Execute(Object parameter) => _execute();
}
XAML:
<DataGrid.Columns>
<DataGridTextColumn Header="Product Code" Binding="{Binding ProductCode}" />
<DataGridTemplateColumn Header="Actions">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Content="Edit" Command="{Binding TriggerVisibility}" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<StackPanel Background="Orange">
<TextBlock Text="Test" />
</StackPanel>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Style.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</Style.Resources>
<Setter Property="DetailsVisibility" Value="{Binding IsDetailsVisibile,
Converter={StaticResource BooleanToVisibilityConverter}}" />
</Style>
</DataGrid.RowStyle>

How to solve the error in naming the control inside custom DataGrid?

I have created the custom data grid in seperate usercontrol called CustomDatagrid. I am using that custom data grid in another usercontrol named CustomUserControl. And I added textblock in the CustomUserControl. While I am trying to run the code, it is showing following error
Cannot set Name attribute value 'txtblock' on element 'TextBlock'.
'TextBlock' is under the scope of element 'SLMDatagrid', which already had a name registered when it was defined in another scope.
My first question is why this error is coming and how to solve this error?
And my requirement is I created one column for radio button. If I checked that RadioButton in particular row, one parameter(e.g Name) in itemsource should display in another column of the same row. If i changed the RadioButton selection, that parameter should not display for previous one but should display for current selected one.
I tried to add a DataTrigger for a Textblock. But it is not working.
Updated the code as below
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary>
<BooleanToVisibilityConverter x:Key="booleanToVisibilityConverter"></BooleanToVisibilityConverter>
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<GridControl:CustomDatagrid x:Name="slmGridTask" Style="{StaticResource DatagridStyle}">
<DataGrid.Columns>
<DataGridTemplateColumn Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<RadioButton Checked="RadioButton_Checked" IsChecked="{Binding IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock x:Name="txtblock" Visibility="{Binding IsChecked, Converter={StaticResource booleanToVisibilityConverter}}" Text="Name">
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</GridControl:CustomDatagrid>
In c# code
private bool isChecked=false;
public event PropertyChangedEventHandler PropertyChanged;
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs ("IsChecked"));
}
}
private void RadioButton_Checked(object sender, RoutedEventArgs e)
{
IsChecked = true;
}
Is there is any way to achieve this?
public ObservableCollection<Item> Items { get; set; } = new ObservableCollection<Item>();
public class Item : INotifyPropertyChanged
{
private bool? isChecked = true;
public event PropertyChangedEventHandler PropertyChanged;
public bool? IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsChecked"));
}
}
public string Text { get; set; }
}
Then your xaml would bind to your view model properties
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Items}">
<DataGrid.Resources>
<BooleanToVisibilityConverter x:Key="booleanToVisibilityConverter"></BooleanToVisibilityConverter>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<RadioButton GroupName="group" IsChecked="{Binding IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}" Visibility="{Binding IsChecked, Converter={StaticResource booleanToVisibilityConverter}}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Which gives something like:

wpf datagrid expand all not working

Hi have a Datagrid with groups, i added a button to Expand All groups but it's not working, all groups stay collapsed.
I'm using PropertyChanged event Handler and a button with a Command
Here is the xaml:
<StackPanel Grid.Row="0">
<Button x:Name="ExpandAll" Content="Tout deplier" VerticalAlignment="Bottom" Command="{Binding ExpandAll}"/>
<!-- This textblock text is updated by the Expanded property changed -->
<TextBlock Text="{Binding Expanded}" />
</StackPanel>
<DataGrid x:Name="GrdLignes" HorizontalAlignment="Stretch" VerticalContentAlignment="Stretch" Margin="0,0,0,0"
Grid.Row="1" VerticalAlignment="Top" AutoGenerateColumns="False" CanUserAddRows="False"
CanUserDeleteRows="False" ItemsSource="{Binding Lignes}" IsReadOnly="True"
RowDetailsVisibilityMode="VisibleWhenSelected" RowHeaderWidth="25">
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander Background="Lavender" IsExpanded="{Binding Expanded}">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" Padding="0,0,5,0" FontWeight="Bold" />
<TextBlock Text="{Binding Path=ItemCount}" Padding="0,0,5,0"/>
<TextBlock Text="Commandes"/>
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Pièce Achat" Binding="{Binding Path=Piece}" FontWeight="Bold"/>
<DataGridTextColumn Header="Type" Binding="{Binding Path=TypeLabel}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Background" Value="{Binding Path=Type, Converter={StaticResource TypeToBrushConverter}}" />
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Statut" Binding="{Binding Path=StatutLabel}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Background" Value="{Binding Path=Statut, Converter={StaticResource StatutToBrushConverter}}" />
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<DataGrid RowHeaderWidth="25" ItemsSource="{Binding Path=Lignes}" AutoGenerateColumns="False" Margin="0" CanUserAddRows="False" CanUserDeleteRows="False" IsReadOnly="True">
<DataGrid.Columns>
<DataGridTextColumn Header="Acheteur" Binding="{Binding Path=Acheteur}"/>
<DataGridTextColumn Header="Pièce" Binding="{Binding Path=Piece}"/>
<DataGridTextColumn Header="Client" Binding="{Binding Path=Client}"/>
<DataGridTextColumn Header="Ref" Binding="{Binding Path=ArRef}"/>
<DataGridTextColumn Header="Ref Fourn" Binding="{Binding Path=RefFourn}"/>
<DataGridTextColumn Header="Designation" Binding="{Binding Path=Designation}"/>
<DataGridTextColumn Header="Qte" Binding="{Binding Path=CmQte}"/>
<DataGridTextColumn Header="Vendeur" Binding="{Binding Path=Vendeur}"/>
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
Here is the viewModel:
public class MainViewModel : INotifyPropertyChanged
{
private bool _expanded = false;
public bool Expanded
{
get { return _expanded; }
set
{
_expanded = value;
OnPropertyChanged("Expanded");
}
}
public ICommand ExpandAll { get; set; }
public MainViewModel()
{
ExpandAll = new Command(ExpandAllAction);
}
private void ExpandAllAction(object parameters)
{
Expanded = true;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I think you need to set the UpdateSource Trigger to "PropertyChanged" when binding to the Expanded Property.
<Expander Background="Lavender" IsExpanded="{Binding Expanded, UpdateSourceTrigger=PropertyChanged}">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" Padding="0,0,5,0" FontWeight="Bold" />
<TextBlock Text="{Binding Path=ItemCount}" Padding="0,0,5,0"/>
<TextBlock Text="Commandes"/>
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
I found a solution from this thread:
https://stackoverflow.com/a/12611779/3182178
I added in MainWindow class this code:
public static class VisualTreeHelper
{
public static Collection<T> GetVisualChildren<T>(DependencyObject current) where T : DependencyObject
{
if (current == null)
return null;
var children = new Collection<T>();
GetVisualChildren(current, children);
return children;
}
private static void GetVisualChildren<T>(DependencyObject current, Collection<T> children) where T : DependencyObject
{
if (current != null)
{
if (current.GetType() == typeof(T))
children.Add((T)current);
for (int i = 0; i < System.Windows.Media.VisualTreeHelper.GetChildrenCount(current); i++)
{
GetVisualChildren(System.Windows.Media.VisualTreeHelper.GetChild(current, i), children);
}
}
}
}
private void ExpandAll_OnClick(object sender, RoutedEventArgs e)
{
Collection<Expander> collection = VisualTreeHelper.GetVisualChildren<Expander>(GrdLignes);
foreach (Expander expander in collection)
expander.IsExpanded = true;
}
private void CollapseAll_OnClick(object sender, RoutedEventArgs e)
{
Collection<Expander> collection = VisualTreeHelper.GetVisualChildren<Expander>(GrdLignes);
foreach (Expander expander in collection)
expander.IsExpanded = false;
}
Then inside the xaml i added two button with this code:
<Button Name="ExpandAll" Content="++" VerticalAlignment="Bottom" Width="30" Click="ExpandAll_OnClick"/>
<Button Name="CollapseAll" Content="--" VerticalAlignment="Bottom" Width="30" Margin="0" Click="CollapseAll_OnClick"/>
It's not the best but it's working...
Using one-way binding to set all groups open or close after a button click command.
View
<UserControl.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
</UserControl.Resources>
<!-- grid code -->
<Expander IsExpanded="{Binding Source={StaticResource proxy}, Path=Data.Expanded, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}">
ViewModel
public bool Expanded
{
get { return _expanded; }
set { _expanded = value; OnPropertyChanged(); }
}
Proxy
public class BindingProxy : Freezable
{
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
public object Data
{
get { return GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataProperty = DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
It's because the binding is done on the group, not on the main model. Replace your XAML code by:
<Expander IsExpanded="{Binding Path=DataContext.IsExpanded, Mode=OneWay,
RelativeSource={RelativeSource AncestorType={x:Type Window}}}">
Note the one way mode: when the user expands or collapses the groups, you don't want to push back the change to your model.

Binding IsReadOnly of a DataGridTextColumn to a DataGridTemplateColumn checkbox IsChecked

Basically, I have a DataGrid with several columns, and I want to enable (changing the IsReadOnly property) a DataGridTextColumn based on a CheckBox IsChecked, located in another DataGridTemplateColumn of the same DataGrid.
Here is (the important part of) the code:
<DataGrid Name="lstTags" Grid.Row="0" ItemsSource="{Binding Path = LinesCollection}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False" SelectionMode="Single" LostFocus="lstTags_LostFocus" SelectionChanged="lstTags_SelectionChanged">
<DataGrid.Columns>
<DataGridTemplateColumn x:Name="colAutoScale" Header="Auto Scale">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox x:Name="ckbAutoScale" HorizontalAlignment="Center" IsChecked="{Binding AutoScale, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Scale" Binding="{Binding Path=Scale}" IsReadOnly="{Binding ElementName ckbAutoScale, Path=IsChecked}" Width="60" />
</DataGrid.Columns>
</DataGrid>
It is worth mentioning that I also want to invert the value of the IsChecked property, that is
IsChecked = true => IsReadOnly = false;
IsChecked = false => IsReadOnly = true.
I would probably achieve this with a simple Converter, but I need that first part working tho.
EDIT:
Answering a good question, my goal is to disable the adjacent cell (same row), not the whole column.
Use below binding for your Scale Column:
<DataGridTextColumn Header="Scale" Binding="{Binding Path=Scale}" Width="60" >
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="IsEnabled" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGridCellsPanel}},Path=Children[0].Content.Content.AutoScale}" />
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
OR simply
<DataGridTextColumn Header="Scale" Binding="{Binding Path=Scale}" Width="60" >
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="IsEnabled" Value="{Binding Path=AutoScale}" />
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
Output:
PS: Above Solution 1 is specific to your code, cause Auto Scale
column is at 0 Index that's why I used Children[0] in
Binding. Please change if there is any contextual need.
This type of problem is really the reason the Model-View-ViewModel (MVVM) pattern exists.
With MVVM, you bind to view models that have the exact properties needed to support the view. This allows the model to be more concerned with what data needs to be persisted.
So, for your problem, you would need to create a LineViewModel, which would look something like this:
public class LineViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool _isAutoScale;
private double _scale;
public bool IsAutoScale
{
get { return _isAutoScale; }
set
{
if (value == _isAutoScale) return;
_isAutoScale = value;
OnPropertyChange("IsAutoScale");
OnPropertyChange("IsReadOnly");
}
}
public double Scale
{
get { return _scale; }
set
{
if (value == _scale) return;
_scale = value;
OnPropertyChange("Scale");
}
}
public bool IsReadOnly => !IsAutoScale;
private void OnPropertyChange(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Meanwhile, you would also want to create a parent view model called MainWindowViewModel (or something that makes sense for your situation). Here is a very crude version:
public class MainWindowViewModel : INotifyPropertyChanged
{
private List<LineViewModel> _lineViewModels;
public event PropertyChangedEventHandler PropertyChanged;
public List<LineViewModel> LineViewModels
{
get { return _lineViewModels; }
set
{
if (value == _lineViewModels) return;
_lineViewModels = value;
OnPropertyChange("LineViewModels");
}
}
public MainWindowViewModel()
{
LineViewModels = new[]
{
new { AutoScale = false, Scale = 0.2 },
new { AutoScale = true, Scale = 0.3 },
new { AutoScale = false, Scale = 0.4 },
}
.Select(
x => new LineViewModel
{
IsAutoScale = x.AutoScale,
Scale = x.Scale
})
.ToList();
}
private void OnPropertyChange(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Finally, you would update your XAML file to look something like this:
<Window x:Class="Sandbox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sandbox="clr-namespace:Sandbox"
mc:Ignorable="d"
Title="MainWindow"
Height="350"
Width="525">
<Window.DataContext>
<sandbox:MainWindowViewModel />
</Window.DataContext>
<DataGrid ItemsSource="{Binding LineViewModels}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
SelectionMode="Single">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Auto Scale">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox HorizontalAlignment="Center"
IsChecked="{Binding IsAutoScale}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Auto Scale">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Scale}"
IsReadOnly="{Binding IsReadOnly}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Window>
So, basically, the view logic for MainWindow is determined by MainWindowViewModel and the view logic for each row of the DataGrid is controlled by a LineViewModel.
Note that a lot of the boilerplate for implementing INotifyPropertyChanged can be simplified using libraries/NuGet packages like MVVM Light Toolkit and PropertyChanged.Fody.

Multi selection in WPF Listview and multiple styles

I have an application in WPF (MVVM).
I've created a view model which points to an ObservableCollections to be shown in the ListView.
The ListView has two custom view resources. 'GridView' and a 'TileView' and buttons to switch between them.
When I select multiple items and change to the other view the selected items are not sync...
after trying to debug I think that, for some reason, when changing views it:
Gets the IsSelected value for every Item in the list (That's OK)
Sets (again..) the Items that have just been set (in the other view..) (?)
The markup:
<Window.Resources>
<local:TileView x:Key="ImageView">
<local:TileView.ItemTemplate >
<DataTemplate >
<StackPanel Width="150" VerticalAlignment="Top">
<Image Source="{Binding Path=ImagePath}"/>
<TextBlock TextWrapping="Wrap" HorizontalAlignment="Center" Text="{Binding Path=PhotoReferenceCode}"/>
</StackPanel>
</DataTemplate>
</local:TileView.ItemTemplate>
</local:TileView>
<GridView x:Key ="ListView">
<GridViewColumn Header="Select" Width="100" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid Width="100">
<!--<CheckBox IsChecked="{Binding Path=IsSelected, Mode=TwoWay}" HorizontalAlignment="Center"/>-->
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Image" Width="100" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding Path=ImagePath}" Stretch="UniformToFill" HorizontalAlignment="Center" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Reference Code" Width="100" DisplayMemberBinding="{Binding Path=PhotoReferenceCode}"/>
</GridView>
</Window.Resources>
<Grid>
<ListView Margin="0,36,0,0" View="{Binding Path=ViewType, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}" SelectionMode="Multiple"
ItemsSource="{Binding Path=InfoList, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
<Button Name="btnListView" Content="ListView" Click="btnListView_Click" HorizontalAlignment="Left" Margin="10,9,0,0" VerticalAlignment="Top" Width="75"/>
<Button Name="btnImageView" Content="ImageView" Click="btnImageView_Click" HorizontalAlignment="Left" Margin="99,9,0,0" VerticalAlignment="Top" Width="75"/>
</Grid>
The Item class:
public class ItemData : INotifyPropertyChanged
{
#region Members
private bool _IsSelected;
public string ImagePath { get; set; }
public string PhotoReferenceCode { get; set; }
#endregion
#region Constractors
public ItemData()
{
}
public ItemData(bool Is_Sected, string Image_Path, int Item_Carat, string Photo_Reference_Code)
{
IsSelected = Is_Sected;
ImagePath = Image_Path;
ItemCarat = Item_Carat;
PhotoReferenceCode = Photo_Reference_Code;
}
#endregion
#region Public
public bool IsSelected
{
get { return _IsSelected; }
set
{
if (this.PropertyChanged != null && _IsSelected != value)
{
_IsSelected = value; OnPropertyChanged("IsSelected");
}
}
}
#endregion
#region Events
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
Any help would be appreciated.
Bind all of the listview's SelectedItems to a property in the view model using TwoWay bind. So the viewModel keeps the selectedItems of the listview and so when the view is changed, the new listview style can pick up the selectedItems from the viewModel

Categories