DataTemplate disappears when moving items in ObservableCollection - wpf

I have a CellTemplate for a column in a ListView. The CellTemplate contains a ComboBox which has an ItemTemplate. Both ItemsSource and SelectedItem is bound to another ViewModel.
The ListView is bound to an ObservableCollection on a ViewModel. Above the ListView there is a toolbar with the buttons to move the selected item up and down. I buttons a bound to and ICommand which will make a Move on the ObservableCollection.
The view is updated fine, but the selected item in the ComboBox is not using the DataTemplate and is just showing the type name.
I found out that everything is working fine if IsEditable = false, but I need this to be true.
I have created a small project that verifies the problem. Perhaps this is an issue in WPF.
Here is XAML:
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:WpfApplication3="clr-namespace:WpfApplication3"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type WpfApplication3:Item}">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
<DataTemplate x:Key="cellTemplate">
<ComboBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" Width="100" IsEditable="true" TextSearch.TextPath="Name"/>
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel>
<ToolBar>
<Button Content="Add" Command="{Binding AddItemCommand}"/>
<Button Content="Up" Command="{Binding MoveItemUpCommand}" CommandParameter="{Binding ElementName=listView, Path=SelectedItem}"/>
<Button Content="Down" Command="{Binding MoveItemDownCommand}" CommandParameter="{Binding ElementName=listView, Path=SelectedItem}"/>
</ToolBar>
<ListView x:Name="listView" ItemsSource="{Binding Collection}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" CellTemplate="{StaticResource cellTemplate}"/>
</GridView>
</ListView.View>
</ListView>
</StackPanel>
</Grid>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
public class ViewModel
{
public ICommand AddItemCommand { get; private set; }
public ICommand MoveItemUpCommand { get; private set; }
public ICommand MoveItemDownCommand { get; private set; }
public ObservableCollection<Row> Collection { get; set; }
public ViewModel()
{
Collection = new ObservableCollection<Row>();
AddItemCommand = new RelayCommand(AddItem);
MoveItemUpCommand = new RelayCommand<Row>(MoveItemUp, CanMoveItemUp);
MoveItemDownCommand = new RelayCommand<Row>(MoveItemDown, CanMoveItemDown);
}
private bool CanMoveItemDown(Row arg)
{
if (arg == null)
return false;
return Collection.Last() != arg;
}
private void MoveItemDown(Row obj)
{
var index = Collection.IndexOf(obj);
Collection.Move(index, index + 1);
}
private bool CanMoveItemUp(Row arg)
{
if (arg == null)
return false;
return Collection.First() != arg;
}
private void MoveItemUp(Row row)
{
var index = Collection.IndexOf(row);
Collection.Move(index, index - 1);
}
private void AddItem()
{
Collection.Add(new Row());
}
}
public class Row
{
public Row()
{
Items = new List<Item> { new Item { Name = "Test1" }, new Item { Name = "Test2" } };
}
public List<Item> Items { get; set; }
public Item SelectedItem { get; set; }
}
public class Item
{
public string Name { get; set; }
public int Order { get; set; }
}

Related

Create a CheckBox that will depend on other CheckBoxes that are inside the ItemsControl

How can I make the CheckBox above (All) depend on the other three in a good way.
That is, if they are all Checked, then it will be Checked, if all are UnChecked, then it will be UnChecked,
And if some of them are Checked, then it will be Null
public ObservableCollection<string> Items { get; set; } = new() { "A", "B", "C" };
<StackPanel>
<CheckBox Content="All"/>
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox/>
<TextBlock Text="{Binding}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
Iterating through UI is a bad idea, it is complicated. You should use mvvm and work with data rather than relying on ui as a data store.
How would that work?
You should bind a collection of item viewmodels. A class exposing both a bool ischecked property and a string description.
I am using the CommunityToolkit.Mvvm package here. That generates code including public properties using attributes.
Here's my viewmodel for each item
using CommunityToolkit.Mvvm.ComponentModel;
namespace WpfApp3
{
public partial class ItemViewModel : ObservableObject
{
[ObservableProperty]
private bool? isChecked;
[ObservableProperty]
private string description;
public ItemViewModel(bool? _isChecked, string _description)
{
isChecked = _isChecked;
description = _description;
}
}
}
I want to bind both those properties and I'll need a window viewmodel exposes a public property for those items.
I'll create a mainwindowviewmodel, but here's the view:
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<StackPanel>
<CheckBox Content="All"
IsChecked="{Binding AllChecked, Mode=TwoWay}"
/>
<ItemsControl ItemsSource="{Binding Items}"
>
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}"
Content="{Binding Description}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</Window>
All is bound to a property in my mainwindowviewmodel.
Here's my mainwindowviewmodel:
using CommunityToolkit.Mvvm.ComponentModel;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
namespace WpfApp3
{
public partial class MainWindowViewModel : ObservableObject
{
[ObservableProperty]
private bool? allChecked = false;
[ObservableProperty]
private ObservableCollection<ItemViewModel> items = new ObservableCollection<ItemViewModel>
{
new ItemViewModel((bool?)null, "A"),
new ItemViewModel(true, "B"),
new ItemViewModel(false, "C"),
};
public MainWindowViewModel()
{
foreach (var item in items)
{
item.PropertyChanged += Item_PropertyChanged;
}
}
private void Item_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsChecked")
{
reDoAll(sender);
}
}
private void reDoAll(object? s)
{
var item = s as ItemViewModel;
Debug.WriteLine(item.Description);
if (items.All(x => x.IsChecked == true))
{
AllChecked = true;
return;
}
if (items.All(x => x.IsChecked == false))
{
AllChecked = false;
return;
}
AllChecked = (bool?)null;
}
}
}
When you change any ischecked, the property changed handler runs redDoAll which applies your logic and sets AllChecked. The All checkbox in the view responds.

WPF: bind my CheckBox into my commnd pure XAML

I try to bind my CheckBox into my commnd.
Base view model
public ViewModelBase()
{
SelectedFileCommand = new SelectedFileCommand(this);
}
<Page.DataContext>
<viewmodel:ViewModelBase/>
</Page.DataContext>
Command
public class SelectedFileCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public ViewModelBase ViewModel { get; set; }
public SelectedFileCommand(ViewModelBase viewModel)
{
ViewModel = viewModel;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
}
}
}
My CheckBox
<CheckBox IsChecked="{Binding IsSelected}"
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding SelectedFileCommand}" CommandParameter="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</CheckBox>
I also Try:
<CheckBox DataContext="{Binding}"
<i:Interaction.Triggers>
<i:EventTrigger EventName="IsChecked">
<i:InvokeCommandAction Command="{Binding SelectedFileCommand}"
CommandParameter="CheckBox.IsChecked"/>
</i:EventTrigger>
</i:Interaction.Triggers>
But my Execute function not called.
EDIT
I forgot to mention that this CheckBox is inside ListViewItem
Working solution
<CheckBox IsChecked="{Binding IsSelected}"
Command="{Binding DataContext.CheckBoxSelectedFileCommand, ElementName=mainView}"
CommandParameter="{Binding IsChecked}"/>
If the checkbox is in a listview when you say Command="{Binding SelectedFileCommand}" you will bind to the listview item's datacontext. If yor command is in the viewmodel of your window this won't work. Something like this will bind to the command that is in your main viewmodel.
Command="{Binding DataContext.SelectedFileCommand, ElementName=mainView}"
Here I gave the window x:Name=mainView. This way I can bind to properties of it's dataContext.
And, IsChecked is not an event you should use "Checked".
Last, the command parameter issue. Since there are two different events for checkbox (Checked/Unchecked) you can use two commands and not pass any parameters. Or you can put a property in the list item viewmodel like;
public bool IsChecked { get; set; }
and you can bind your checkbox's IsChecked property to this property. And finally you can bind command parameter to this new property.
Edit: Full example
<Window x:Class="WpfApp2.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:i="http://schemas.microsoft.com/expression/2010/interactivity"
x:Name="mainView"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ListView ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Text}" IsChecked="{Binding IsChecked}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding DataContext.SelectedFileCommand, ElementName=mainView}"
CommandParameter="{Binding IsChecked}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</CheckBox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Window>
Codebehind:
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;
namespace WpfApp2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
}
public class MainViewModel
{
public ObservableCollection<ItemViewModel> Items { get; set; } = new ObservableCollection<ItemViewModel>();
public ICommand SelectedFileCommand { get; set; }
public MainViewModel()
{
SelectedFileCommand = new SelectedFileCommand(this);
this.Items.Add(new ItemViewModel() { Text = "Item 1" });
this.Items.Add(new ItemViewModel() { Text = "Item 2" });
this.Items.Add(new ItemViewModel() { Text = "Item 3" });
this.Items.Add(new ItemViewModel() { Text = "Item 4" });
}
}
public class ItemViewModel
{
public string Text { get; set; }
public bool IsChecked { get; set; }
}
public class SelectedFileCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public MainViewModel ViewModel { get; set; }
public SelectedFileCommand(MainViewModel viewModel)
{
ViewModel = viewModel;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
var x = parameter;
}
}
}

Binding more properties into GridViewColumnHeader

I would like to make GridView with column header, for example like exampe below (example for one column). My DataTemplate for header contains have more controls (TextBlock and TextBox) and I am not able to figure out how to set binding to access individual controls from template (header). In my example, instead of "aaa" to set value of HColumn1Line2 property.
<ListView Name="PeopleList" ItemsSource="{Binding People}">
<ListView.Resources>
<DataTemplate x:Key="MyHeaderTemplate">
<StackPanel>
<TextBlock Text="{Binding}"/>
<TextBox Text="aaa"></TextBox>
</StackPanel>
</DataTemplate>
</ListView.Resources>
<ListView.View>
<GridView ColumnHeaderTemplate="{StaticResource MyHeaderTemplate}">
<GridViewColumn Header="{Binding HColumn1Line1}" DisplayMemberBinding="{Binding PeopleListItem}"/>
</GridView>
</ListView.View>
</ListView>
//Code
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
PeopleList.DataContext = this;
_people.Add(new Person() { PeopleListItem = "some information" });
HColumn1Line1 = "Header line #1";
HColumn1Line2 = "text instead of aaa";
}
public List<Person> People { get { return _people; } }
List<Person> _people = new List<Person>();
public string HColumn1Line1 { get; set; }
public string HColumn1Line2 { get; set; }
}
public class Person
{
public string PeopleListItem { get; set; }
}
The main problem you have is you are using the MainWindow instance as DataContext for the ListView which is in the MainWindow Content this will run you into exceptions such as "Logical tree depth exceeded while traversing the tree.", so you better make your DataContext a seperate object such as the following:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
PeopleList.DataContext = new ViewModel();
}
}
public class ViewModel
{
public List<Person> People { get { return _people; } }
List<Person> _people = new List<Person>();
public string HColumn1Line1 { get; set; } = "Header line #1";
public string HColumn1Line2 { get; set; } = "text instead of aaa";
public ViewModel()
{
_people.Add(new Person() { PeopleListItem = "some information" });
}
}
public class Person
{
public string PeopleListItem { get; set; }
}
Updated XAML:
<ListView Name="PeopleList" ItemsSource="{Binding People}">
<ListView.Resources>
<DataTemplate x:Key="MyHeaderTemplate">
<StackPanel>
<TextBlock Text="{Binding HColumn1Line1}"/>
<TextBox Text="{Binding HColumn1Line2}"></TextBox>
</StackPanel>
</DataTemplate>
</ListView.Resources>
<ListView.View>
<GridView ColumnHeaderTemplate="{StaticResource MyHeaderTemplate}">
<GridViewColumn Header="{Binding}" DisplayMemberBinding="{Binding PeopleListItem}"/>
</GridView>
</ListView.View>
</ListView>

WPF binding with ContentControl not working

I'm just starting to use WPF, but my binding doesn't work.
When I start the application the screen is just blank.
This is my XAML
<Window x:Class="HelloWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ContentControl Content="{Binding PersonOne}" Width="auto" Height="auto" >
<ContentControl.ContentTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding FirstName}" FontSize="15" />
<TextBlock Text="{Binding Age}" FontSize="12" />
</StackPanel>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</Grid>
This is the code:
public partial class MainWindow : Window
{
public Person PersonOne;
public MainWindow()
{
InitializeComponent();
PersonOne = new Person();
PersonOne.Gender = Gender.Female;
PersonOne.Age = 24;
PersonOne.FirstName = "Jane";
PersonOne.LastName = "Joe";
this.DataContext = this;
}
}
And this is the person class
public class Person
{
public string LastName { get; set; }
public string FirstName { get; set; }
public int Age { get; set; }
public Gender Gender { get; set; }
}
public enum Gender
{
Male,
Female
}
What am I doing wrong?
You cannot bind to fields, only properties, so change this:
public Person PersonOne;
to this:
public Person PersonOne {get;set;}
BTW, you probably need to create a ViewModel rather than putting the Data inside the Window itself.

Specify DataContext in code

I have a simple View that I want to bind to my ViewModel. I am currently using the Source= format for the data binding, but would like to convert that into specifying the DataContext in code.
This is what I have and it is working ...
XAML:
<Window.Resources>
<local:ViewModel x:Key="ViewModel" />
</Window.Resources>
<Button Content="Click">
<local:EventToCommand.Collection>
<local:EventToCommandCollection>
<local:EventToCommand Event="Click" Command="{Binding Source={StaticResource ViewModel}, Path=ClickCommand, diag:PresentationTraceSources.TraceLevel=High}" />
<local:EventToCommand Event="GotFocus" Command="{Binding Source={StaticResource ViewModel}, Path=GotFocusCommand}" />
</local:EventToCommandCollection>
</local:EventToCommand.Collection>
</Button>
</Window>
ViewModel Code:
public class ViewModel
{
public Command ClickCommand { get; set; }
public Command GotFocusCommand { get; set; }
public ViewModel()
{
ClickCommand = new Command((obj) => { Execute(obj); return null; });
GotFocusCommand = new Command((obj) => { Execute(obj); return null; });
}
void Execute(object param)
{
if (param != null)
System.Windows.MessageBox.Show(param.ToString());
else
System.Windows.MessageBox.Show("Execute");
}
}
Now all I want to do is this in my Window's code behind :
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
and remove the Window.Resources section in XAML, but I can not figure out how I should change my Binding strings accordingly.
The DataContext is the default Source, so this should work:
<local:EventToCommand Event="GotFocus" Command="{Binding GotFocusCommand}" />

Resources