Filtering animals using wpf radiobuttons - c#

I am playing with a sample WPF application that is tiered in a Model-View-Presenter manner. The Model is a collection of Animals which is displayed in a view via binding to properties in a presenter class. The XAML has an items control that displays all the animals in a model.
The model class has a boolean attribute called 'IsMammal'. I want to introduce a filter in the XAML in the form of a radio button group that filters the collection of animals based on the 'IsMammal' attribute. Selection of the radiobutton 'Mammals' updates the items control with all the Animals that have the 'IsMammal' value set to true and when the value is toggled to 'Non-Mammals', the display is updated with all animals that have that particular boolean set to false. The logic to do the filtering is extremely simple. What is troubling me is the placement of the logic. I don't want any logic embedded in the *.xaml.cs. I want the toggling of the radiobutton to trigger a logic in the presenter that tapers my animal collection to be rebound to the display.
Some guidance here will be extremely appreciated.
Thanks

I suggest you do the following in your Presenter:
=> Create a ListCollectionView field. Set it to be equal to the "Default Collection View" of your collection, and use it as the ItemsSource for the list control. Something like:
public class Presenter()
{
private ListCollectionView lcv;
public Presenter()
{
this.lcv = (ListCollectionView)CollectionViewSource.GetDefaultView(animalsCollection);
listControl.ItemsSource = this.lcv;
}
}
=> In the handler/logic for the RadioButton's corresponding event, filter the ListCollectionView. Something like:
void OnCheckedChanged()
{
bool showMammals = radioButton.IsChecked;
this.lcv.Filter = new Predicate((p) => (p as Animal).IsMammal == showMammals);
this.lcv.Refresh();
}
Hope this helps.
EDIT:
While doing this is possible using MVP, using MVVM should be a better choice, IMHO (and as mentioned by the other answer). To help you out, I wrote a sample that implements your requirements via MVVM. See below:
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Grid>
<StackPanel Orientation="Vertical">
<RadioButton x:Name="rb1" GroupName="MyGroup" Content="IsMammal = true" Checked="rb1_Checked"/>
<RadioButton x:Name="rb2" GroupName="MyGroup" Content="IsMammal = false" Checked="rb2_Checked"/>
<ListBox ItemsSource="{Binding Path=AnimalsCollectionView}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>
</Window>
Code-behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using System.Threading;
using System.ComponentModel;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void rb1_Checked(object sender, RoutedEventArgs e)
{
(this.DataContext as ViewModel).UpdateFilter(true);
}
private void rb2_Checked(object sender, RoutedEventArgs e)
{
(this.DataContext as ViewModel).UpdateFilter(false);
}
}
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<Animal> animals = new ObservableCollection<Animal>();
private ListCollectionView animalsCollectionView;
public ListCollectionView AnimalsCollectionView
{
get { return this.animalsCollectionView; }
}
public void UpdateFilter(bool showMammals)
{
this.animalsCollectionView.Filter = new Predicate<object>((p) => (p as Animal).IsMammal == showMammals);
this.animalsCollectionView.Refresh();
}
public ViewModel()
{
this.animals.Add(new Animal() { Name = "Dog", IsMammal = true });
this.animals.Add(new Animal() { Name = "Cat", IsMammal = true });
this.animals.Add(new Animal() { Name = "Bird", IsMammal = false });
this.animalsCollectionView = (ListCollectionView)CollectionViewSource.GetDefaultView(this.animals);
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
#endregion
}
public class Animal : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return this.name; }
set
{
this.name = value;
this.OnPropertyChanged("Name");
}
}
private bool isMammal;
public bool IsMammal
{
get { return this.isMammal; }
set
{
this.isMammal = value;
this.OnPropertyChanged("IsMammal");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
#endregion
}
}

I came from an MVP background before learning WPF and I have come to find that adapting the MVP pattern to WPF is a difficult exercise at best. The "proper" (read, least frustrating) approach is to utilize the Model-View-ViewModel (MVVM) pattern, which makes heavy use of the databinding features of WPF to minimize the amount of code that ends up the view-behind file.
In the simplest case, you would end up with two properties on your ViewModel:
bool FilterMammals
ObservableCollection MammalsToDisplay
In the XAML, You would bind the first to your radio button group and the second to the ItemsSource of the ListBox. The WPF databinding framework will call your property setter whenever the radiobutton group value changes, and in here you can filter and then update the list of items.
I'm not sure what your familiarity with MVVM is, so I'll stop here. Let me know if more detail would help :).

Related

MVVM, Can you bind command in ViewModel to Button in ItemsRepeater when ItemsSource is ObservableCollection<Model> and a property of the ViewModel?

I am building a WinUI 3 application that presents a user with a list of Animal IDs, along with a Button beside each ID that the user can click to view information about that specific Animal. I am using an ItemsRepeater, whose ItemsSource is bound to an ObservableCollection<Animal> inside an AnimalViewModel, so I can create a list of TextBox with a Button beside it using a DataTemplate -- the TextBox will have as its value the Animal.ID. I also want to use the MVVM design pattern to accomplish this, but this is my first time trying it.
For now, I want the ability to click a Button and have it display a dialog box with the Animal.ID it corresponds to (the TextBox adjacent to it). I have tried implementing this by using an ICommand and defining a function to display a MessageDialog in my AnimalViewModel, then defining the custom ICommand in its own file.
The issue seems to be that since I have bound my ItemsRepeater to ObservableCollection<Animal> in my AnimalViewModel, I can't bind the Button to the command in my AnimalViewModel. This leads me to believe I am not using MVVM correctly or I have improperly structured my code, but I am not sure what to change/how to move forward.
Error
BindingExpression path error: 'DisplayIDsCommand' property not found on 'MAIT.Models.Animal'
Below is the relevant code. I am able to create the list but I cannot get the Button to exhibit the desired behavior (please forgive any glaring errors, I had to simplify the code for this question):
MainWindow.xaml
<Window
x:Class="MAIT.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MAIT"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:muxc="using:Microsoft.UI.Xaml.Controls" xmlns:l="using:MAIT.Models"
mc:Ignorable="d">
<Grid x:Name="MainGrid">
<Grid.Resources>
<muxc:StackLayout x:Name="VerticalStackLayout" Orientation="Vertical" Spacing="8"/>
<DataTemplate x:Key="AnimalTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="text" Text="{Binding ID}"></TextBlock>
<Button Command="{Binding DisplayIDsCommand}" CommandParameter="{Binding ElementName=text, Path=Text}">View</Button>-
</StackPanel>
</DataTemplate>
</Grid.Resources>
<ScrollViewer HorizontalScrollBarVisibility="Auto" HorizontalAlignment="Center" VerticalAlignment="Center"
HorizontalScrollMode="Auto"
IsVerticalScrollChainingEnabled="False"
MaxHeight="500">
<muxc:ItemsRepeater
ItemsSource="{Binding Path=Animals}"
Layout="{StaticResource VerticalStackLayout}"
ItemTemplate="{StaticResource AnimalTemplate}"/>
</ScrollViewer>
</Grid>
</Window>
MainWindow.xaml.cs
using Microsoft.UI.Xaml;
using MAIT.ViewModels;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace MAIT
{
/// <summary>
/// An empty window that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
MainGrid.DataContext = new AnimalViewModel();
}
}
}
Animal.cs
using System.ComponentModel;
using System.Diagnostics;
namespace MAIT.Models
{
internal class Animal : INotifyPropertyChanged
{
string _ID;
public Animal(string id)
{
ID = id;
}
public string ID
{
get
{
return _ID;
}
set
{
_ID = value;
OnPropertyChanged("ID");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
AnimalViewModel.cs
using MAIT.Commands;
using MAIT.Models;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data;
using System.Data.OleDb;
using System.Diagnostics;
using Windows.UI.Popups;
namespace MAIT.ViewModels
{
internal class AnimalViewModel : INotifyPropertyChanged
{
public IDsCommand DisplayIDsCommand;
public AnimalViewModel()
{
Animals = new ObservableCollection<Animal>();
DisplayIDsCommand = new IDsCommand(DisplayIDs);
GetAnimals();
}
private void GetAnimals()
{
for(int i = 0; i < 3; i++)
{
Animal animal = new Animal(i.ToString());
Animals.Add(animal);
}
}
public async void DisplayIDs(string id)
{
MessageDialog t = new MessageDialog(id);
await t.ShowAsync();
}
private ObservableCollection<Animal> _Animals;
public ObservableCollection<Animal> Animals
{
get
{
return _Animals;
}
set
{
_Animals = value;
OnPropertyChanged("Animals");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
IDsCommand.cs
using MAIT.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace MAIT.Commands
{
internal class IDsCommand : ICommand
{
public event EventHandler CanExecuteChanged;
private Action<string> _Execute;
public IDsCommand(Action<string> execute)
{
_Execute = execute;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_Execute.Invoke(parameter as string);
}
}
}
your first WPF appears to be well-on-its-way.
If you want to bind to your command, you will need to use a private field along with the getter and setter, such as with the Animals property, and call OnPropertyChanged in its setter as well. This will let the button know when a command is assigned!
Here are a few friendly pointers if you are interested about learning MVVM:
Consider replacing the using in the local parameter to xmlns:local="clr-namespace:MAIT".
In MVVM DataContext property can be set in the Window's constructor but usually it's put in the XALM above <Grid> as
<Window.DataContext>
<viewModels:AnimalViewModel/>
</Window.DataContext>
Because this context will not allow you to use local.ViewModels, you can declare another xmlns as xmlns:viewModels="clr-namespace:MAIT.ViewModels"
If you are using recent .NET versions, you might appreciate using PropertyChanged?.Invoke(this, new(propertyName)); as the method body for OnPropertyChanged. Moreover, you can use System.Runtime.CompilerServices to automatically get the calling member's name by changing the OnPropertyChanged's signature to private void OnPropertyChanged([CallerMemberName] string propertyName = "").
To be able to bind the Command property to a source property of a parent element, you need to get a reference to the parent element somehow. In Win UI, you cannot use something RelativeSource AncestorType=ItemsRepeater.
You could workaround this by creating an attached property that sets the DataContext of the Button to the parent ItemsRepeater:
public static class AncestorSource
{
public static readonly DependencyProperty AncestorTypeProperty =
DependencyProperty.RegisterAttached(
"AncestorType",
typeof(Type),
typeof(AncestorSource),
new PropertyMetadata(default(Type), OnAncestorTypeChanged)
);
public static void SetAncestorType(FrameworkElement element, Type value) =>
element.SetValue(AncestorTypeProperty, value);
public static Type GetAncestorType(FrameworkElement element) =>
(Type)element.GetValue(AncestorTypeProperty);
private static void OnAncestorTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement target = (FrameworkElement)d;
if (target.IsLoaded)
SetDataContext(target);
else
target.Loaded += OnTargetLoaded;
}
private static void OnTargetLoaded(object sender, RoutedEventArgs e)
{
FrameworkElement target = (FrameworkElement)sender;
target.Loaded -= OnTargetLoaded;
SetDataContext(target);
}
private static void SetDataContext(FrameworkElement target)
{
Type ancestorType = GetAncestorType(target);
if (ancestorType != null)
target.DataContext = FindParent(target, ancestorType);
}
private static object FindParent(DependencyObject dependencyObject, Type ancestorType)
{
DependencyObject parent = VisualTreeHelper.GetParent(dependencyObject);
if (parent == null)
return null;
if (ancestorType.IsAssignableFrom(parent.GetType()))
return parent;
return FindParent(parent, ancestorType);
}
}
Usage:
<DataTemplate x:Key="AnimalTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="text" Text="{Binding ID}"></TextBlock>
<Button local:AncestorSource.AncestorType="muxc:ItemsRepeater"
Command="{Binding DataContext.DisplayIDsCommand}"
CommandParameter="{Binding ElementName=text, Path=Text}">View</Button>-
</StackPanel>
</DataTemplate>
Please refer to this blog post for more information.

C# WPF DataBinding - Data not Updating

There are several "WPF Databinding Not Updating" questions on here, and I've read through them all, including the top-ranked one:
WPF DataBinding not updating?
However, I'm swapping out entire objects in my situation, and what's worse is that when I try to create a reproducible version of what I'm doing, it works in that version:
<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:local="clr-namespace:WpfApp2"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Label Content="Contact Address" />
<TextBox x:Name="txtContactAddress" Text="{Binding Path=Contact.Address, Mode=OneWay}" />
<Label Content="Organization Address" />
<TextBox x:Name="txtOrgAddress" Text="{Binding Path=Organization.Address, Mode=OneWay}" />
<Button Content="Next Contact" Click="Button_Click" />
</StackPanel>
</Window>
Code-Behind:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApp2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public RecordWrapper CurrentRecord;
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
CurrentRecord = new RecordWrapper(new ContactData());
this.DataContext = CurrentRecord;
}
}
public class RecordWrapper : INotifyPropertyChanged
{
private ContactData _Contact;
public ContactData Contact
{
get { return _Contact; }
set
{
_Contact = value;
OnPropertyChanged("Contact");
Organization = _Contact.Organization;
}
}
private OrganizationData _Organization;
public OrganizationData Organization
{
get { return _Organization; }
set
{
_Organization = value;
OnPropertyChanged("Organization");
}
}
public RecordWrapper(ContactData contactData)
{
Contact = contactData;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
#endregion
}
public class ContactData
{
public string Address { get; set; }
public OrganizationData Organization { get; set; }
public ContactData()
{
Address = new Random().Next(1000, 9999) + " Contact Street";
Organization = new OrganizationData();
}
}
public class OrganizationData
{
public string Address { get; set; }
public OrganizationData()
{
Address = new Random().Next(1000, 9999) + " Org Street";
}
}
}
So this particular sample works as expected - when I click on the "Next Contact" button, it generates a new RecordWrapper object that contains a new ContactData and new OrganizationData object, and then assigns the new object to DataContext and then I see the textboxes update properly.
Since I'm replacing the entire object in the DataContext, I can even ditch the property notifications and reduce the RecordWrapper down to just:
public class RecordWrapper
{
public ContactData Contact { get; set; }
public OrganizationData Organization { get; set; }
public RecordWrapper(ContactData contactData)
{
Contact = contactData;
Organization = Contact.Organization;
}
}
...and it still works great.
My real code, however, reflects the first example.
In the real code, it seems like updating this.DataContext with a new object doesn't work. Nothing is updated, but there are no errors, either. Setting the path in XAML doesn't work, either. In fact, the only way I can get it to work is to use code-behind to set the bindings directly to the data object:
BindingOperations.SetBinding(txtContactAddress, TextBox.TextProperty, new Binding() { Source = this.RecordWrapper, Path = new PropertyPath("Contact.Address"), Mode = BindingMode.OneWay });
When I do this, I can see it working, but that's obviously missing the point of binding if I have to run code on each field each time to update the bindings.
I'm stumped because the real code is nearly identical to the example aside from having more fields, different field names, and differences that have nothing to do with data or binding. When I click on a button, this is the exact code:
this.Data = new DataBindings(CurrentContact.FetchFake());
this.DataContext = this.Data;
The FetchFake() method generates a new CurrentContact object with some fake data, and I have verified that after that first line runs, this.Data.CurrentContact.Login is populated and is a read-only property of the CurrentContact object (not a field).
...and one of the XAML textboxes looks like this:
<TextBox x:Name="txtAccountNumber" Grid.Column="1" Grid.Row="1" Width="Auto" Text="{Binding Path=CurrentContact.Login, Mode=OneWay}"/>
The result is blank. I have also tried variations on order (e.g. setting DataContext to this.Data first, then setting the this.Data.CurrentContact property and letting the property-changed notification kick off, but still no luck. But if I run:
BindingOperations.SetBinding(tabAccount.txtAccountNumber, TextBox.TextProperty, new Binding() { Source = this.Data, Path = new PropertyPath("CurrentContact.Login"), Mode = BindingMode.OneWay });
...then I'll see the value show up.
Based on these symptoms, and the fact that my first example works but the real code doesn't, can anyone think of any place I should look for some culprits?
Ughhhhh. Figured it out. I had a REALLY old line of code in there that changed the DataContext of a container element that was between the Window and the Textbox, which interrupted the correct binding. I thought I had removed all of it, but hadn't. It DID throw an error, as Erno de Weerd had suggested, but the error was lost among some of the startup logging. We can close this one out.

Data Grid control not showing ObservableCollection data

I'm rather new to WPF and C# in general. I'm playing around with it and encountered a problem which I feel like would be a piece of cake for an expert but I have no idea what I'm doing wrong.
I'm trying to create a simple DataGrid control (within a TabControl) and bind it to an ObservableCollection object.
I use microsoft's Data Binding Demo provided in their data binding overview as a basis for my code.
MainWindow XAML:
<Window x:Class="PetProject.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:local="clr-namespace:PetProject"
mc:Ignorable="d"
Title="PetProject" Height="350" Width="525">
<Window.Resources>
<CollectionViewSource
Source="{Binding Source={x:Static Application.Current}, Path=Dogs}"
x:Key="DogsDataView" />
</Window.Resources>
<Grid Margin="8,8,8,8">
<TabControl>
<TabItem Header="Dogs">
<DataGrid ItemsSource="{Binding Source={StaticResource DogsDataView}}">
</DataGrid>
</TabItem>
</TabControl>
</Grid>
</Window>
Code-Behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace PetProject
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
///
public partial class MainWindow : Window
{
CollectionViewSource DogsDataView;
public MainWindow()
{
InitializeComponent();
DogsDataView = (CollectionViewSource)(this.Resources["DogsDataView"]);
}
}
}
The App XAML is
<Application x:Class="PetProject.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PetProject"
Startup="AppStartup">
<!--StartupUri="MainWindow.xaml"-->
</Application>
code-behind:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace PetProject
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
private ObservableCollection<Dog> dogs = new ObservableCollection<Dog>();
void AppStartup(object sender, StartupEventArgs args)
{
LoadData();
MainWindow mainWindow = new MainWindow();
mainWindow.Show();
}
public ObservableCollection<Dog> Dogs
{
get { return this.dogs; }
set { this.dogs = value; }
}
private void LoadData() {
Dog Johnny = new Dog("Johnny",1325);
Dog Diamond = new Dog("Diamond",1327);
this.Dogs.Add(Johnny);
this.Dogs.Add(Diamond);
}
}
}
Dog is just a class implementing the INotifyPropertyChanged interface (which for now does not do anything):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace PetProject
{
public class Dog : INotifyPropertyChanged
{
private string name;
private string number;
public event PropertyChangedEventHandler PropertyChanged;
public Dog(string name, int number)
{
this.name = name;
this.number = number.ToString("D4");
}
protected void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
I'd appreciate any help in understanding why the DataGrid is not populated.
Also, any suggestion on bad coding habits or improvement to the code would be very welcome, as I'm in a very initial learning-by-experience phase.
Thanks!
You can't bind to private fields. You can only bind to public properties. As far as the DataGrid is concerned, Dog has no information to display.
public class Dog : INotifyPropertyChanged
{
private string _name;
private string _number;
public Dog(string name, int number)
{
Name = name;
Number = number.ToString("D4");
}
public String Name
{
get { return _name; }
set
{
if (value != _name)
{
_name = value;
NotifyPropertyChanged(nameof(Name));
}
}
}
public String Number
{
get { return _number; }
set
{
if (value != _number)
{
_number = value;
NotifyPropertyChanged(nameof(Number));
}
}
}
I'm prefixing your private fields with underscores because that's standard practice. It's standard practice because having two identifiers which differ only by case is a recipe for confusion and bugs.
First of all, I suggest you to read about MVVM principles then maybe choose a MVVM framework to use with WPF. For instance MVVM light toolkit is a good choice to start and understand MVVM.
For your example, here are just a few remarks about your code:
I suggest you to group all your 'business' data into a viewModel class (see MVVM practices all around the web) - nothing in the App class...
This ViewModel will implement 'INotifyPropertyChanged' interface
So the Dogs property will be located into this ViewModel and will raise the 'PropertyChanged' event in its setter (what is not currently the case in your sample)
There are several MVVM frameworks that would 'bind' automatically your views to your view model, but to understand, the main goal is to set your Window.DataContext with the appropriate ViewModel.
That's why you can restore in App.xaml: StartupUri="MainWindow.xaml"
Then to load your ViewModel, you can do something like that to load your Dogs collection:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
// For test: LOAD & SET your DataContext here
//
var myDogViewmodel = new DogViewModel();
myDogViewModel.LoadData();
this.DataContext = myDogViewmodel;
}
}
Your ViewModel should look like something like that:
public class DogViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<Dog> _dogs;
public ObservableCollection<Dog> Dogs
{
get { return _dogs; }
set
{
_dogs = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Dogs"));
}
}
public void LoadData()
{
// ....
}
}
Then your Dog class must also implement INotifuPropertyChanged interface:
public class Dog : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _name;
private int _number;
public string Name
{
get => _name;
set
{
if (_name != value)
{
_name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name"));
}
}
}
public int Number
{
get => _number;
set
{
if (_number != value)
{
_number = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Number"));
}
}
}
}
Finally,in your MainWindow.xaml:
>
<Window x:Class="PetProject.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:local="clr-namespace:PetProject"
mc:Ignorable="d"
Title="PetProject" Height="350" Width="525">
<Grid Margin="8,8,8,8">
<TabControl>
<TabItem Header="Dogs">
<DataGrid ItemsSource="{Binding Dogs}" />
</TabItem>
</TabControl>
</Grid>
It should work now ;) Tell me if it's clear. Get familiar with MVVM...

Proper way of implementing MVVM Pattern in WPF

I just started learning WPF coming from Java Swing and WinForms. I decided to try something new to learn other concepts and technologies for developing programs. Last time, I was introduced on the concept of MVC Pattern. For what I have learned, it is a way of separating the UI logic, business logic, and data. I found out that one of the key concepts of WPF is Binding and the MVVM Pattern.
Here is a part of my code where i tried implementing MVVM.
MainWindowModel.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows.Controls;
namespace DocNanzDCMS.Model
{
public class MainWindowModel : INotifyPropertyChanged
{
private PropertyChangedEventArgs pce;
public MainWindowModel()
{
pce = new PropertyChangedEventArgs("");
}
private UserControl userControl;
#region ControlProperty
public UserControl ContentProperty {
get
{
return userControl;
}
set
{
userControl = value;
PropertyChanged(this, pce);
}
}
#endregion
private DateTime dateTime;
#region DateProperty
public String DateProperty
{
get
{
return dateTime.ToLongDateString();
}
set
{
dateTime = DateTime.Parse(value);
PropertyChanged(this, pce);
}
}
#endregion
public String TimeProperty
#region TimeProperty
{
get
{
return dateTime.ToLongTimeString();
}
set
{
dateTime = DateTime.Parse(value);
PropertyChanged(this, pce);
}
}
#endregion
private String title;
public String TitleProperty
#region TitleProperty
{
get
{
return title;
}
set
{
title = value;
PropertyChanged(this, pce);
}
}
#endregion
public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
}
}
MainWindowViewModel.cs
using DocNanzDCMS.Model;
using DocNanzDCMS.View;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace DocNanzDCMS.ViewModel
{
public class MainWindowViewModel
{
private MainWindow mainWindow;
private MainWindowModel mainWindowModel;
private Thread mainWindowThread;
private LoginModel loginModel;
private LoginViewModel loginViewModel;
private LoginView loginView;
private String title;
public MainWindowViewModel(MainWindowModel mainWindowModel, MainWindow mainWindow)
{
this.mainWindowModel = mainWindowModel;
this.mainWindow = mainWindow;
initialize();
}
private void initialize()
{
loginModel = new LoginModel();
loginView = new LoginView();
loginViewModel = new LoginViewModel(loginModel, loginView);
mainWindow.DataContext = mainWindowModel;
mainWindowThread = new Thread(BackgroundProcess);
mainWindowThread.IsBackground = true;
mainWindowThread.Start();
gotoLogin();
}
private void BackgroundProcess()
{
while(true)
{
updateTitle();
updateTime();
try
{
Thread.Sleep(100);
}
catch(ThreadInterruptedException e)
{
}
}
}
public void gotoLogin()
{
mainWindowModel.ContentProperty = loginView;
title = "Login";
}
private void updateTime()
{
mainWindowModel.DateProperty = DateTime.Now.ToString();
mainWindowModel.TimeProperty = DateTime.Now.ToString();
}
public void updateTitle()
{
mainWindowModel.TitleProperty = "Doc Nanz Dental | "+title;
}
}
}
MainWindow.cs
using DocNanzDCMS.Model;
using DocNanzDCMS.ViewModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace DocNanzDCMS
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private MainWindowModel mainWindowModel;
private MainWindowViewModel mainWindowViewModel;
public MainWindow()
{
InitializeComponent();
initializeApp();
}
private void initializeApp()
{
mainWindowModel = new MainWindowModel();
mainWindowViewModel = new MainWindowViewModel(mainWindowModel, this);
}
}
}
MainWindow.xaml
<Window x:Class="DocNanzDCMS.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:local="clr-namespace:DocNanzDCMS"
mc:Ignorable="d"
Title="{Binding TitleProperty}" Height="600" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="75"/>
<RowDefinition Height="*"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<!--Banner-->
<Grid Grid.Row="0" Background="AliceBlue">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="225"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<!--Date and Time Panel-->
<Grid Grid.Column="2" Background="Aquamarine">
<Grid.RowDefinitions>
<RowDefinition Height="1.5*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--Date Background-->
<StackPanel Grid.Row="0" Background="BurlyWood"/>
<!--Date-->
<Label Grid.Row="0" Content="{Binding DateProperty}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<!--Time Background-->
<StackPanel Grid.Row="1" Background="BlanchedAlmond"/>
<!--Time-->
<Label Grid.Row="1" Content="{Binding TimeProperty}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</Grid>
<!--Content-->
<ScrollViewer Grid.Row="1" Content="{Binding ContentProperty}"/>
<!--Status Bar-->
<Grid Grid.Row="2">
</Grid>
</Grid>
</Window>
I created a Model and View and manipulated those in ViewModel. I am not sure if this is a proper way of implementing MVVM or is it even an MVVM, because I am seeing it as MVC pattern.
On Wikipedia, it says:
The components are, Model, View, ViewModel, and Binder.
This part of my code displays a window with a banner, and on rightmost part of the banner are labels that displays date and time. It works, but my concern is if the way I made it is actually following MVVM pattern.
For the sake of MVVM the View Model should not contain a reference to the View (is considered bad practice)
Is the View that know the ViewModel and not the opposite.
The View knows the ViewModel which in turn know the Model (or Models)
The INotifyPropertyChanged interface should be implemented in the ViewModel to permits the view to update itself through binding (in some circustances is perfectly legit to implement the interface on the Model also).
Keep in mind that the ViewModel can be seen as a Model adapted to the need of the View, so with this in mind i prefer to leave the Model classes as simple POCO objects and write the INotifyPropertyChanged implementation on the ViewModel
The ViewModel become the DataContext of the View (you can assign the DataContext in the View's constructor in code behind or in the xaml).
For navigating through views you could use (at minimum) 2 approaches
You should decide if you want a View-first approach or a ViewModel-first approach.
In the View-First approach when you want to navigate to a new page you create a View and some mechanism (the binder) will create the respective ViewModel (which in turn create or obtain the Model)
In the ViewModel first approach you create a new ViewModel (which in turn create or obtain the Model) and the binder will create the respective View.
Based on what i told you, here is an example:
View (MainWindowView.cs), we assign the DataContext:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel()
}
}
ViewModel (MainWindowViewModel.cs):
namespace DocNanzDCMS.ViewModel
{
public class MainWindowViewModel : INotifyPropertyChanged
{
private MainWindowModel mainWindowModel;
public Model {get {return mainWindowModel;}}
public MainWindowViewModel()
{
this.mainWindowModel = new mainWindowModel();
}
}
}
Model (MainWindowModel.cs) :
public class MainWindowModel
{
private PropertyChangedEventArgs pce;
public MainWindowModel()
{
pce = new PropertyChangedEventArgs("");
}
private UserControl userControl;
#region ControlProperty
public UserControl ContentProperty {
get
{
return userControl;
}
set
{
userControl = value;
PropertyChanged(this, pce);
}
}
#endregion
private DateTime dateTime;
#region DateProperty
public String DateProperty
{
get
{
return dateTime.ToLongDateString();
}
set
{
dateTime = DateTime.Parse(value);
PropertyChanged(this, pce);
}
}
#endregion
public String TimeProperty
#region TimeProperty
{
get
{
return dateTime.ToLongTimeString();
}
set
{
dateTime = DateTime.Parse(value);
PropertyChanged(this, pce);
}
}
#endregion
private String title;
public String TitleProperty
#region TitleProperty
{
get
{
return title;
}
set
{
title = value;
PropertyChanged(this, pce);
}
}
#endregion
public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
}
Also, i think you should look at some framework like prism or caliburn micro (i prefer the first one) to assist you in the correct implementation on the MVVM pattern and not reinventing the wheel (as a plus you will get also a navigation system, to navigate between views).
Your question is very broad but here are some thoughts.
A view model shouldn't know anything about the view. Instead of injecting the MainWindowViewModel with a reference to the MainWindow, you should simply set the DataContext of the MainWindow to an instance of the view model:
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
The MainWindowViewModel can then initialize and/or communicate with the model while the view binds to the view model.
Also, a view model shouldn't expose any UIElements such as for example a UserControl. UIElements are defined in the view.
After several nights of studying, I finally "absorbed" the concept of MVVM and its difference with MVC.
Model - Most of the time they are just classes with properties. Like User, Product etc...
View - The user interface. RegisterUserView, LoginView, AddProductView.
ViewModel - This is where the action happens. ViewModel manipulates the model based on the requirements/rules and exposes it for View. But ViewModel does not know the existence of View.
Binding - This is the glue between the View and the ViewModel.
In comparison with MVC (Just my opinion),
View in MVC is passive while View in MVVM is active. The Controller in MVC decides what contents should be displayed in View, while in MVVM, View performs the binding making it responsible on what it should display.
WPF is so much pain, but it is really powerful.

How to sort a treeview that has stackpanel items

i tried to create somthing to quickly locate and watch files. So i created a TreeView that has StackPanels as Items. A StackPanel contains an Image an a Label.
private TreeViewItem createFile(string Name, string soureFile)
{
TreeViewItem tvi = new TreeViewItem();
StackPanel sp = new StackPanel();
Image i = new Image();
Label l_Text = new Label();
Label l_FileName = new Label();
l_FileName.Content = soureFile;
l_FileName.Width = 0;
l_Text.Content = Name;
System.Drawing.Bitmap dImg = (System.Drawing.Bitmap)Properties.Resources.ResourceManager.GetObject("Picture");
MemoryStream ms = new MemoryStream();
dImg.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
BitmapImage bImg = new BitmapImage();
bImg.BeginInit();
bImg.StreamSource = new MemoryStream(ms.ToArray());
bImg.EndInit();
i.Source = bImg;
i.Height = 20;
i.Width = 20;
sp.Name = "SP_File";
sp.Orientation = Orientation.Horizontal;
sp.Children.Add(i);
sp.Children.Add(l_Text);
sp.Children.Add(l_FileName);
tvi.Header = sp;
return tvi;
}
One can create logical folders (just for creating a structure) and add files and other folders to the folders (just references to to the actual file on the hdd). This worked fine until i tried to sort the TreeView. I read stuff on on Sorting TreeViews with
SortDescriptions.Add(new SortDescription("Header", ListSortDirection.Ascending));
Obviously this doesn't work for me since i cant exchange "Header" with "Header.StackPanel.Label.Text"
As I read a little bit further it seems I used the wrong approach to the whole thing by not using MVVM (Numerically sort a List of TreeViewItems in C#).
Since I have litte to no experience with MVVM can someone explain to me how it is best do this with MVVM? I use a List of "watchedFile" to keep the files and folders.
I basically have the the following class for a file
class watchedFile
{
public string name { get; private set; }
public string path { get; private set; }
public List<string> tags { get; private set; }
public watchedFile(string Name, string Path, List<string> Tags)
{
name = Name;
path = Path;
tags = Tags;
}
}
If path is null or empty its a folder. The TreeViewItem has a little Image which shows a little "folder" or "picture", a label which shows the "watchedFile.name" and an invisible label which contains the watchedfile.path (which is only shown as a tooltip). I guess I should do this with DataBinding so I dont need to add an invisible Label.
Questions:
How can I solve the task using MVVM?
How/where can I bind the Image to the TreeViewItem when I have just the wacthedFile.path to distinguish?
How do I sort the watched items?
How do I keep track of the TreeView level (so i can rebuild the structure from a saved file)?
Is there a way to sort a TreeView with StackPanel Items without using MVVM/Data-Binding?
Any help is highly appreciated.
Here's how to do this MVVM fashion.
First, write viewmodel classes. Here, we've got a main viewmodel that has a collection of WatchedFile instances, and then we've got the WatchedFile class itself. I've also decided to make Tag a class, instead of just using strings. This lets us write data templates in the XAML that explicitly and exclusively will be used to display Tag instances, rather than strings in general. The UI is full of strings.
The notification properties are tedious to write if you don't have a snippet. I have snippets (Steal them! They're not nailed down!).
Once you've got this, sorting is no big deal. If you want to sort the root level items, those are WatchedFile.
SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
But we'll do that in XAML below.
Serialization is simple, too: Just make your viewmodel classes serializable. The important thing here is that your sorting and serialization don't have to care what's in the treeview item templates. StackPanels, GroupBoxes, whatever -- it doesn't matter at all, because your sorting and serialization code just deals with your data classes, not the UI stuff. You can change the visual details in the data templates radically without having to worry about it affecting any other part of the code. That's what's nice about MVVM.
Viewmodels:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace WatchedFile.ViewModels
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class WatchedFile : ViewModelBase
{
#region Name Property
private String _name = default(String);
public String Name
{
get { return _name; }
set
{
if (value != _name)
{
_name = value;
OnPropertyChanged();
}
}
}
#endregion Name Property
#region Path Property
private String _path = default(String);
public String Path
{
get { return _path; }
set
{
if (value != _path)
{
_path = value;
OnPropertyChanged();
}
}
}
#endregion Path Property
#region Tags Property
private ObservableCollection<Tag> _tags = new ObservableCollection<Tag>();
public ObservableCollection<Tag> Tags
{
get { return _tags; }
protected set
{
if (value != _tags)
{
_tags = value;
OnPropertyChanged();
}
}
}
#endregion Tags Property
}
public class Tag
{
public Tag(String value)
{
Value = value;
}
public String Value { get; private set; }
}
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
Populate();
}
public void Populate()
{
// Arbitrary test info, just for display.
WatchedFiles = new ObservableCollection<WatchedFile>
{
new WatchedFile() { Name = "foobar.txt", Path = "c:\\testfiles\\foobar.txt", Tags = { new Tag("Testfile"), new Tag("Text") } },
new WatchedFile() { Name = "bazfoo.txt", Path = "c:\\testfiles\\bazfoo.txt", Tags = { new Tag("Testfile"), new Tag("Text") } },
new WatchedFile() { Name = "whatever.xml", Path = "c:\\testfiles\\whatever.xml", Tags = { new Tag("Testfile"), new Tag("XML") } },
};
}
#region WatchedFiles Property
private ObservableCollection<WatchedFile> _watchedFiles = new ObservableCollection<WatchedFile>();
public ObservableCollection<WatchedFile> WatchedFiles
{
get { return _watchedFiles; }
protected set
{
if (value != _watchedFiles)
{
_watchedFiles = value;
OnPropertyChanged();
}
}
}
#endregion WatchedFiles Property
}
}
Code behind. Note I only added one line here to what the wizard gave me.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WatchedFile
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModels.MainViewModel();
}
}
}
And lastly the XAML:
<Window
x:Class="WatchedFile.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:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
xmlns:local="clr-namespace:WatchedFile"
xmlns:vm="clr-namespace:WatchedFile.ViewModels"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<CollectionViewSource
x:Key="SortedWatchedFiles"
Source="{Binding WatchedFiles}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Name" Direction="Ascending" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</Window.Resources>
<Grid>
<TreeView
ItemsSource="{Binding Source={StaticResource SortedWatchedFiles}}"
>
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type vm:WatchedFile}"
ItemsSource="{Binding Tags}"
>
<TextBlock
Text="{Binding Name}"
ToolTip="{Binding Path}"
/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate
DataType="{x:Type vm:Tag}"
>
<TextBlock
Text="{Binding Value}"
/>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
</Window>
The XAML is less than obvious. TreeView.Resources is in scope for any child of the TreeView. The HierarchicalDataTemplates don't have an x:Key property, which makes them implicit. That means that when the TreeView's items are instantiated, each of the root items will have a WatchedFile class instance for its DataContext. Since WatchedFile has an implicit data template, that will be used to fill in its content. TreeView is recursive, so it uses HierarchicalDataTemplate instead of regular DataTemplate. HierarchicalDataTemplate adds the ItemSource property, which tells the item where to look for its children on its DataContext object. WatchedFile.Tags is the ItemsSource for the root-level tree items. Those are Tag, which has its own implicit HierarchicalDataTemplate. It doesn't have children so it doesn't use HierarchicalDataTemplate.ItemsSource.
Since all our collections are ObservableCollection<T>, you can add and remove items from any collection at any time and the UI will update automagically. You can do the same with the Name and Path properties of WatchedFile, because it implements INotifyPropertyChanged and its properties raise PropertyChanged when their values change. The XAML UI subscribes to all the notification events without being told, and does the right thing -- assuming you've told it what it needs to know to do that.
Your codebehind can grab SortedWatchedFiles with FindResource and change its SortDescriptions, but this makes more sense to me, since it's agnostic about how you're populating the treeview:
<Button Content="Re-Sort" Click="Button_Click" HorizontalAlignment="Left" />
<!-- ... snip ... -->
<TreeView
x:Name="WatchedFilesTreeView"
...etc. as before...
Code behind:
private void Button_Click(object sender, RoutedEventArgs e)
{
var cv = CollectionViewSource.GetDefaultView(WatchedFilesTreeView.ItemsSource);
cv.SortDescriptions.Clear();
cv.SortDescriptions.Add(
new System.ComponentModel.SortDescription("Name",
System.ComponentModel.ListSortDirection.Descending));
}
or the non MVVM solution would have been....
I see your header is a StackPanel with 2 children and you whish to sort on the Content of the label, which is the 2nd child
You would access the label child as an array of position [1] since arrays are 0 based.
TreeView1.Items.SortDescriptions.Clear();
TreeView1.Items.SortDescriptions.Add(new SortDescription("Header.Children[1].Content", ListSortDirection.Ascending));
TreeView1.Items.Refresh();

Categories

Resources