WPF: ObservableCollection Memory leak - c#

[I'm sorry for my bad English]
In my MainWindow I have some ContentControl and I set his Content to some view called SimpleView. If that SimpleView has ListBox bounded to Collection, The view is alive even if I remove the SimpleView.
All my viewmodels are Implementing INotifyPropertyChanged.
This is my code:
MainWindow.xaml
<Window x:Class="WpfMemory.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>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Content="Set view" Click="SetView" />
<Button Content="Clear view" Click="ClearView" />
</StackPanel>
<ContentControl x:Name="ViewContainer" Grid.Row="1" Margin="4" />
</Grid>
</Window>
In code behind
private void SetView(object sender, RoutedEventArgs e)
{
var simpleViewModel = new ViewModels.SimpleViewModel();
var simpleview = new Views.SimpleView() { DataContext = simpleViewModel };
ViewContainer.Content = simpleview;
}
private void ClearView(object sender, RoutedEventArgs e)
{
ViewContainer.Content = null;
System.GC.Collect();
}
SampleView.xaml
<UserControl x:Class="WpfMemory.Views.SimpleView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition MinWidth="60"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="SomeText" />
<TextBox Text="{Binding SomeText}" Grid.Column="1"/>
<TextBlock Text="Large data" Grid.Row="1"/>
<ListBox ItemsSource="{Binding LargeData,Mode=OneTime}"
Grid.Row="2" Grid.Column="1"
DisplayMemberPath="Name" />
</Grid>
</UserControl>
View models
public sealed class SimpleViewModel : ViewModelBase
{
public SimpleViewModel()
{
var items = Enumerable.Range(1, 10000)
.Select(x => new SimpleItem()
{
Id = x,
Name = "Item " + x
})
.ToArray();
LargeData = new ObservableCollection<SimpleItem>(items);
}
public string SomeText{ get; set; } = "yehudah";
ObservableCollection<SimpleItem> largeData;
public ObservableCollection<SimpleItem> LargeData
{
get { return largeData; }
set { SetProperty(ref largeData, value); }
}
}
public sealed class SimpleItem : ViewModelBase
{
public int Id { get; set; }
public string Name { get; set; }
}
I run the application and Click on SetView and then on ClearView.
Now, in vs diagnostic tools I click on "Take snapshot" And this is the result:
Thanks for any help.

I think you should reset simpleview.DataContext when content control is unloaded.
You can do
simpleview.Unloaded += (_, __) => simpleview.DataContext = null;
when SetView.

Related

Dynamically add User Controls based on List in WPF with MVVM pattern

I have two views for my application. Looking at the first View, UpdaterMainView, you can see that I create a Grid with two columns. The left column is split even further, into 5 rows. What I want to do is fill the five rows from a list of objects that are stored in the UpdaterMainViewModel, TaskList. I created a custom User Control called TaskView which is the layout of how I want to display the information of the list, a button, and a textbox.
When I instantiate the UpdaterMainViewModel, my application scans a directory for a few things and creates a list of tasks to complete. There are only five total possible tasks, hence why I created five rows. If the conditions aren't met and Task 2 doesn't need to be run, I don't want to show the UserControl and the one below it, should move up. I don't want to use the code-behind unless it can still meet the guidelines of MVVM.
For testing, I added <local:TaskView Grid.Column="0" Grid.Row="0"/> to the UpdaterMainView and created a default constructor. But, what I need, is to add TaskView for each item in TaskList. To get this to work, I also have to create the UserControls with a different constructor. When you use DataContext, it uses a parameterless constructor. I somehow need to use the constructor with the enum parameter to fill the grid with each appropriate TaskView.
UpdaterMainView:
<UserControl x:Class="POSUpdaterGUI.View.UpdaterMainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:POSUpdaterGUI.View" xmlns:local1="clr-namespace:POSUpdaterGUI.ViewModel"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<local1:UpdaterMainViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition Height="60" />
<RowDefinition Height="60" />
<RowDefinition Height="60" />
<RowDefinition Height="60" />
<RowDefinition Height="60" />
</Grid.RowDefinitions>
<local:TaskView Grid.Column="0" Grid.Row="0"/>
<local:TaskView Grid.Column="0" Grid.Row="1"/>
<local:TaskView Grid.Column="0" Grid.Row="2"/>
<local:TaskView Grid.Column="0" Grid.Row="3"/>
</Grid>
<TextBox Grid.Column="1" Margin="10" TextWrapping="Wrap" Text="{Binding OutputText}"/>
</Grid>
</UserControl>
UpdaterMainViewModel:
using POSUpdaterLibrary;
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace POSUpdaterGUI.ViewModel
{
public class UpdaterMainViewModel : INotifyPropertyChanged
{
private string outputText;
public string OutputText
{
get => outputText;
set
{
outputText = value;
NotifyPropertyChanged("OutputText");
}
}
public UpdateTaskList TaskList { get; set; }
public UpdaterMainViewModel()
{
Log("Application Starting.");
TaskList = new UpdateTaskList();
TaskList.LoggerEvent += TaskList_LoggerEvent;
TaskList.GetList(AppContext.BaseDirectory);
}
private void TaskList_LoggerEvent(object sender, LoggerEventArgs e)
{
Log(e.LogString);
}
private void Log(string message)
{
if (OutputText == null)
{
OutputText = DateTime.Now.ToString() + " - " + message;
return;
}
OutputText += "\n" + DateTime.Now.ToString() + " - " + message;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
TaskView:
<UserControl x:Class="POSUpdaterGUI.View.TaskView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:POSUpdaterGUI.View" xmlns:local1="clr-namespace:POSUpdaterGUI.ViewModel"
mc:Ignorable="d"
d:DesignHeight="60" d:DesignWidth="500">
<UserControl.DataContext>
<local1:TaskViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" HorizontalAlignment="Center" Margin="0,0,0,0" VerticalAlignment="Center" Height="50" Width="50" Command="{Binding RunTaskCommand}">
<Image Source="{Binding StatusImage}"/>
</Button>
<Label Grid.Column="1" Content="{Binding TaskName}" HorizontalAlignment="Left" Margin="15,0,0,0" VerticalAlignment="Center"/>
</Grid>
TaskViewModel:
using POSUpdaterLibrary;
using System;
using System.ComponentModel;
using System.Windows.Input;
namespace POSUpdaterGUI.ViewModel
{
class TaskViewModel : INotifyPropertyChanged
{
public string TaskName { get; set; }
public ICommand RunTaskCommand { get; set; }
public string StatusImage { get; set; }
public TaskViewModel()
{
TaskName = "undefined";
RunTaskCommand = new RelayCommand(new Action<object>(DefaultMethod));
StatusImage = STARTIMAGE;
}
public TaskViewModel(UpdaterConstants.TaskType taskType)
{
switch (taskType)
{
case UpdaterConstants.TaskType.KillProcesses:
TaskName = "Processes Killed";
RunTaskCommand = new RelayCommand(new Action<object>(DefaultMethod));
StatusImage = STARTIMAGE;
break;
case UpdaterConstants.TaskType.FileCopy:
TaskName = "Files Copied";
RunTaskCommand = new RelayCommand(new Action<object>(DefaultMethod));
StatusImage = STARTIMAGE;
break;
case UpdaterConstants.TaskType.DatabaseUpdates:
TaskName = "Database Updated";
RunTaskCommand = new RelayCommand(new Action<object>(DefaultMethod));
StatusImage = STARTIMAGE;
break;
case UpdaterConstants.TaskType.COMDLLsRegistered:
TaskName = "COM DLLs Registered";
RunTaskCommand = new RelayCommand(new Action<object>(DefaultMethod));
StatusImage = STARTIMAGE;
break;
case UpdaterConstants.TaskType.FileCleanup:
TaskName = "Files Cleaned";
RunTaskCommand = new RelayCommand(new Action<object>(DefaultMethod));
StatusImage = STARTIMAGE;
break;
default:
break;
}
}
public enum Status
{
START,
COMPLETE,
FAILED,
WAIT
}
internal const string STARTIMAGE = "/data/start.png";
internal const string COMPLETEIMAGE = "/data/complete.png";
internal const string FAILEDIMAGE = "/data/failed.png";
internal const string WAITIMAGE = "/data/wait.png";
public event PropertyChangedEventHandler PropertyChanged;
private void DefaultMethod(object obj)
{
// Shouldn't be shown. Just a hold-over
throw new NotImplementedException();
}
}
}
The UpdaterMainView is the main view of the application and is always showing. To prove this, here is my MainWindow.xaml:
<Window
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:POSUpdaterGUI"
xmlns:View="clr-namespace:POSUpdaterGUI.View" x:Class="POSUpdaterGUI.MainWindow"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<View:UpdaterMainView/>
</Grid>
Thanks #Clemens for pointing me in the right direction! I did not need a separate View. I did need to use Data Templating.
Here is what I ended up with for the UpdaterMainView:
<UserControl x:Class="POSUpdaterGUI.View.UpdaterMainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:POSUpdaterGUI.View" xmlns:local1="clr-namespace:POSUpdaterGUI.ViewModel"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<local1:UpdaterMainViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0" Margin="10" ItemsSource="{Binding TaskList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" HorizontalAlignment="Center" Margin="0,0,0,0" VerticalAlignment="Center" Height="50" Width="50" Command="{Binding Command}">
<Image Source="{Binding StatusImage}"/>
</Button>
<Label Grid.Column="1" Content="{Binding TaskName}" HorizontalAlignment="Left" Margin="15,0,0,0" VerticalAlignment="Center"/>
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox Grid.Column="1" Margin="10" TextWrapping="Wrap" Text="{Binding OutputText}"/>
</Grid>
</UserControl>
Here is what I ended up with for the UpdaterMainViewModel:
using POSUpdaterGUI.Models;
using POSUpdaterLibrary;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace POSUpdaterGUI.ViewModel
{
public class UpdaterMainViewModel : INotifyPropertyChanged
{
private UpdaterLogger _logger;
private string outputText;
public string OutputText
{
get => outputText;
set
{
outputText = value;
NotifyPropertyChanged("OutputText");
}
}
private List<UpdateTaskWrapper> taskList;
public List<UpdateTaskWrapper> TaskList
{
get => taskList;
set
{
taskList = value;
NotifyPropertyChanged("TaskList");
}
}
public UpdaterMainViewModel()
{
_logger = UpdaterLogger.Instance;
_logger.LoggerEvent += Logger_LoggerEvent;
Log("Application Starting.");
TaskList = GetTaskList();
}
private void Logger_LoggerEvent(object sender, LoggerEventArgs e)
{
Log(e.LogString);
}
private List<UpdateTaskWrapper> GetTaskList()
{
UpdateTaskList list = new UpdateTaskList();
var myList = list.GetList(AppContext.BaseDirectory);
// Create one out of the Wrapper class.
List<UpdateTaskWrapper> wrappedList = new List<UpdateTaskWrapper>();
foreach (var task in myList)
{
wrappedList.Add(new UpdateTaskWrapper(task));
}
return wrappedList;
}
private void Log(string message)
{
if (OutputText == null)
{
OutputText = DateTime.Now.ToString() + " - " + message;
return;
}
OutputText += "\n" + DateTime.Now.ToString() + " - " + message;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
The most important parts were that I removed the TaskView, Added Datatemplating (between the tags), and changed the List from a custom UpdaterTaskList to List.

Input to and Output from User Control in WPF

I made this minimalistic project to learn output and input with user control and it's working as intended. I want to ask, is this a good approach or is there something which is not necessary?
I also want to post this, because there is tons of post with specific user cases, but not one with a simple example to learn binding mechanics.
Main Window:
<Window x:Class="OutputFromUserControl.View.OutputFromUserControlWindow"
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:OutputFromUserControl.View"
xmlns:uc="clr-namespace:OutputFromUserControl.View.Controls"
xmlns:vm="clr-namespace:OutputFromUserControl.ViewModel"
mc:Ignorable="d"
Title="Output From User Control" Height="450" Width="800">
<Window.DataContext>
<vm:MainVM x:Name="MainVM"/>
</Window.DataContext>
<StackPanel HorizontalAlignment="Left">
<Label Content="Form elements:"/>
<Border CornerRadius="5" BorderBrush="Blue" BorderThickness="1">
<Grid HorizontalAlignment="Left" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Label Content="Name Input: " Grid.Row="0" Grid.Column="0"/>
<TextBox Grid.Row="0" Grid.Column="1"
Text="{Binding NameInput, UpdateSourceTrigger=PropertyChanged}"
Width="200"
/>
<Label Content="Surname Input: " Grid.Row="1" Grid.Column="0"/>
<TextBox Grid.Row="1" Grid.Column="1"
Text="{Binding SurnameInput, UpdateSourceTrigger=PropertyChanged}"
Width="200"
/>
<Label Content="Name Output from Control: " Grid.Row="2" Grid.Column="0"/>
<TextBlock Grid.Row="2" Grid.Column="1"
Text="{Binding FullName}"
Width="200"
/>
</Grid>
</Border>
<Label Content="User Control:" Margin="0,10,0,0"/>
<Border CornerRadius="5" BorderBrush="Red" BorderThickness="1">
<uc:NameConcatControl x:Name="NameUC"
NameInput="{Binding NameInput}"
SurnameInput="{Binding SurnameInput}"
NameOutput="{Binding FullName, Mode=TwoWay}"
/>
</Border>
</StackPanel>
</Window>
MainVM:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
namespace OutputFromUserControl.ViewModel
{
public class MainVM : INotifyPropertyChanged
{
private string nameInput;
public string NameInput {
get { return nameInput; }
set
{
nameInput = value;
OnPropertyChanged(nameof(NameInput));
}
}
private string surnameInput;
public string SurnameInput {
get { return surnameInput; }
set {
surnameInput = value;
OnPropertyChanged(nameof(SurnameInput));
}
}
private string fullName;
public string FullName {
get { return fullName; }
set {
fullName = value;
OnPropertyChanged(nameof(FullName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Control xaml:
<UserControl x:Class="OutputFromUserControl.View.Controls.NameConcatControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:OutputFromUserControl.View.Controls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Label Content="Name Input: " Grid.Row="0" Grid.Column="0"/>
<TextBlock Grid.Row="0" Grid.Column="1"
Text="{Binding NameInput}"
x:Name="NameInputTextBlock"
/>
<Label Content="Surname Input: " Grid.Row="1" Grid.Column="0"/>
<TextBlock Grid.Row="1" Grid.Column="1"
Text="{Binding SurnameInput}"
x:Name="SurnameInputTextBlock"
/>
<Label Content="Name Output: " Grid.Row="2" Grid.Column="0"/>
<TextBlock Grid.Row="2" Grid.Column="1"
Text="{Binding NameOutput}"
x:Name="OutputNameTextBlock"
/>
</Grid>
</UserControl>
User control .cs:
using System.Windows;
using System.Windows.Controls;
namespace OutputFromUserControl.View.Controls
{
/// <summary>
/// Interaction logic for NameConcatControl.xaml
/// </summary>
public partial class NameConcatControl : UserControl
{
public string NameInput {
get { return (string)GetValue(NameInputProperty); }
set { SetValue(NameInputProperty, value); }
}
public static string defaultNameInput = "NameInput";
public static readonly DependencyProperty NameInputProperty =
DependencyProperty.Register("NameInput", typeof(string), typeof(NameConcatControl), new PropertyMetadata(defaultNameInput, SetNameOutput));
public string SurnameInput {
get { return (string)GetValue(SurnameInputProperty); }
set { SetValue(SurnameInputProperty, value); }
}
public static string defaultSurnameInput = "Surname Input";
public static readonly DependencyProperty SurnameInputProperty =
DependencyProperty.Register("SurnameInput", typeof(string), typeof(NameConcatControl), new PropertyMetadata(defaultSurnameInput, SetNameOutput));
public string NameOutput {
get { return (string)GetValue(NameOutputProperty); }
set { SetValue(NameOutputProperty, value); }
}
public static string defaultNameOutput = "Name Output";
public static readonly DependencyProperty NameOutputProperty =
DependencyProperty.Register("NameOutput", typeof(string), typeof(NameConcatControl), new PropertyMetadata(defaultNameOutput));
private static void SetNameOutput(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
NameConcatControl control = (NameConcatControl)d;
string nameInput = "";
string surnameInput = "";
if(e.Property.Name == "NameInput")
{
string newValue = (string)e.NewValue;
nameInput = string.IsNullOrEmpty(newValue) ? "" : newValue;
}
else
{
nameInput = string.IsNullOrEmpty(control.NameInputTextBlock.Text)
? ""
: control.NameInputTextBlock.Text;
}
if(e.Property.Name == "SurnameInput")
{
string newValue = (string)e.NewValue;
surnameInput = string.IsNullOrEmpty(newValue) ? "" : newValue;
}
else
{
surnameInput = string.IsNullOrEmpty(control.SurnameInputTextBlock.Text)
? ""
: control.SurnameInputTextBlock.Text;
}
string fullName = $"{nameInput} {surnameInput}";
control.OutputNameTextBlock.Text = fullName;
control.NameOutput = fullName;
}
public NameConcatControl()
{
InitializeComponent();
}
}
}
This question has a very wide answers. Different people with different approaches can use for their applications.
But we always follow one common formula.
Each view - will have its own view model. (Again in this approach, someone might say might not be true all the time).
From your code (xaml and code), below are my observations.
<Window.DataContext>
<vm:MainVM x:Name="MainVM"/>
</Window.DataContext>
I generally don't like setting data context in xaml. Instead I prefer to set it on the code-behind (mostly from constructor)
Instead of creating a dependency properties in user control and bind the MainVM properties to the dependency properties of User control.
I prefer to do it this way.
I prefer to create a separate UserControlViewModel.cs and add required properties to it.
public class UserControlViewModel : INotifyPropertyChanged
{
private string nameInput;
public string NameInput {
get { return nameInput; }
set
{
nameInput = value;
OnPropertyChanged(nameof(NameInput));
}
}
private string surnameInput;
public string SurnameInput {
get { return surnameInput; }
set {
surnameInput = value;
OnPropertyChanged(nameof(SurnameInput));
}
}
private string fullName;
public string FullName {
get { return fullName; }
set {
fullName = value;
OnPropertyChanged(nameof(FullName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Then I prefer to add this as a property in MainVM.cs
public class MainVM : INotifyPropertyChanged
{
private UserControlViewModel _userControlViewModel;
public UserControlViewModel UserControlViewModel
{
get { return _userControlViewModel; }
set
{
_userControlViewModel = value;
OnPropertyChanged(nameof(UserControlViewModel));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
// Rest of your code
// You don't need existing properties any more here.
// If you want to access these properties from MainVM then use the UserControlViewModel property and access the members of it.
}
Then I prefer to set the data-context of my UserControl to this property like below in my MainWindow.xaml
<uc:NameConcatControl x:Name="NameUC" ="{Binding UserControlViewModel}" />
My usercontrol contorl binding's still remain same as the property names are same and we moved to UserControlViewModel.cs
Now you can remove all dependency properties from code behind of UserControl.xaml.cs
Note :- As I stated at the beginning of my answer, this question has wide area for answers and there are lot of possibilities to answer this question.
I hope I have tried to give you some inputs from my end. I guess this should give you some idea to develop rest..
You can try making those changes and let me know in case if you face any errors or binding issues.
Assuming you just want the full-name view to be something like "Surname, Name", you could actually remove the FullName property from your view model, and just use a MultiBinding (btw the StringFormat property can be used with both MultiBindings and regular Bindings, its pretty nifty if you aren't familiar with it).
As for the Labels, it's good to make a habit of using the simplest control required to get the job done, and in this case, TextBlocks would do just fine, since you don't appear to be using any of the extended functionality the Label offers (i.e., BorderBrush, Padding, ContentTemplate, etc.).
You don't generally need to create your own dependency properties in UserControl derived classes, since they are usually designed with a particular viewmodel in mind. They are more useful when the view is independent from the viewmodel, and the dependency properties serve as an api, through which other controls/viewmodels can interact with it.
<Window x:Class="OutputFromUserControl.View.OutputFromUserControlWindow"
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:OutputFromUserControl.View"
xmlns:uc="clr-namespace:OutputFromUserControl.View.Controls"
xmlns:vm="clr-namespace:OutputFromUserControl.ViewModel"
mc:Ignorable="d"
Title="Output From User Control" Height="450" Width="800">
<Window.DataContext>
<vm:MainVM x:Name="MainVM"/>
</Window.DataContext>
<StackPanel HorizontalAlignment="Left">
<Label Content="Form elements:"/>
<Border CornerRadius="5" BorderBrush="Blue" BorderThickness="1">
<Grid HorizontalAlignment="Left" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<TextBlock Text="Name Input:" Grid.Row="0" Grid.Column="0"/>
<TextBox Grid.Row="0" Grid.Column="1"
Text="{Binding NameInput, UpdateSourceTrigger=PropertyChanged}"
Width="200"
/>
<TextBlock Text="Surname Input:" Grid.Row="1" Grid.Column="0"/>
<TextBox Grid.Row="1" Grid.Column="1"
Text="{Binding SurnameInput, UpdateSourceTrigger=PropertyChanged}"
Width="200"
/>
<TextBlock Text="Name Output from Control:" Grid.Row="2" Grid.Column="0"/>
<TextBlock Grid.Row="2" Grid.Column="1" Width="200">
<MultiBinding StringFormat="{}{0}, {1}">
<Binding Path="SurnameInput"/>
<Binding Path="NameInput"/>
</MultiBinding>
</TextBlock>
</Grid>
</Border>
<Label Content="User Control:" Margin="0,10,0,0"/>
<Border CornerRadius="5" BorderBrush="Red" BorderThickness="1">
<uc:NameConcatControl x:Name="NameUC"
NameInput="{Binding NameInput}"
SurnameInput="{Binding SurnameInput}"
NameOutput="{Binding FullName}"
/>
</Border>
</StackPanel>

Dynamically filtering a CollectionViewSource bound to a SelectedItem via MVVM

I've been digging back into some projects in WPF, and come across a hurdle that I haven't been able to find a directly related solution for.
Essentially I want to filter a child property of a SelectedItem dynamically (via text entered in the filter box, something along the lines of .Contains(filter)). The UI displays correctly in the sample project, but after attempting to implement solutions from every hit possible on SO or otherwise, I've come up blank, or making serious compromises to the MVVM pattern.
ParentItem:
public class ParentItem
{
public string Name { get; set; }
public List<string> ChildItems { get; set; }
public DateTime CreatedOn { get; set; }
public bool IsActive { get; set; }
public ParentItemStatus Status { get; set; }
}
public enum ParentItemStatus
{
Status_One,
Status_Two
}
ViewModel:
public class MainWindowViewModel : ViewModelBase
{
public ObservableCollection<ParentItem> ParentItems { get; set; }
public MainWindowViewModel()
{
ParentItems = new ObservableCollection<ParentItem>();
LoadDummyParentItems();
}
private ICommand _filterChildrenCommand;
public ICommand FilterChildrenCommand => _filterChildrenCommand ?? (_filterChildrenCommand = new RelayCommand(param => FilterChildren((string)param), param => CanFilterChildren((string)param)));
private bool CanFilterChildren(string filter)
{
//TODO: Check for selected item in real life.
return filter.Length > 0;
}
private void FilterChildren(string filter)
{
//TODO: Filter?
}
private void LoadDummyParentItems()
{
for (var i = 0; i < 20; i++)
{
ParentItems.Add(new ParentItem()
{
Name = $"Parent Item {i}",
CreatedOn = DateTime.Now.AddHours(i),
IsActive = i % 2 == 0 ? true : false,
Status = i % 2 == 0 ? ParentItemStatus.Status_Two : ParentItemStatus.Status_One,
ChildItems = new List<string>() { $"Child one_{i}", $"Child two_{i}", $"Child three_{i}", $"Child four_{i}" }
});
}
}
}
MainWindow:
<Window x:Class="FilteringDemo.Views.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:FilteringDemo.Views"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<CollectionViewSource x:Key="ChildItemsViewSource" Source="{Binding ElementName=ItemList, Path=SelectedItem.ChildItems}" />
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".25*"/>
<ColumnDefinition Width=".75*"/>
</Grid.ColumnDefinitions>
<ListView x:Name="ItemList" Grid.Column="0" Margin="2" ItemsSource="{Binding ParentItems}" SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding ElementName=ItemList, Path=SelectedItem.Name}" Margin="2"/>
<TextBlock Grid.Column="1" Text="{Binding ElementName=ItemList, Path=SelectedItem.CreatedOn}" Margin="2"/>
<TextBlock Grid.Column="2" Text="{Binding ElementName=ItemList, Path=SelectedItem.IsActive}" Margin="2"/>
<TextBlock Grid.Column="3" Text="{Binding ElementName=ItemList, Path=SelectedItem.Status}" Margin="2"/>
</Grid>
<ListView Grid.Row="1" Margin="2" ItemsSource="{Binding Source={StaticResource ChildItemsViewSource}}" />
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="Contains:" Margin="2" VerticalAlignment="Center"/>
<TextBox x:Name="ChildFilterInput" Grid.Column="1" Margin="2" />
<Button Grid.Column="2" Content="Filter" Width="100" Margin="2" Command="{Binding FilterChildrenCommand}" CommandParameter="{Binding ElementName=ChildFilterInput, Path=Text}"/>
</Grid>
</Grid>
</Grid>
</Window>
I've tried various implementations of adding a Filter event handler on the CollectionViewSource but have been unable to make them dynamic. It also seems like most examples/tutorials only deal directly with the parent item or static filters.
In a non-MVVM mindset, I was thinking to have an interaction trigger drive the selected item back into the ViewModel, and then create a filtered ICollectionView which the ChildItems ListView would bind to, but it seems like I can't be the only person trying this, and that there must be an easier MVVM binding friendly way.
The following example shows a simple solution to implement live filtering on a collection:
Person.cs
class Person
{
public Person(string firstName, string lastName)
{
this.FirstName = firstName;
this.LastName = lastName;
}
public string FirstName { get; set; }
public string LastName { get; set; }
}
ViewModel.cs
class ViewModel
{
public ViewModel()
{
this.Persons = new ObservableCollection<Person>()
{
new Person("Derek", "Zoolander"),
new Person("Tony", "Montana"),
new Person("John", "Wick"),
new Person("The", "Dude"),
new Person("James", "Bond"),
new Person("Walter", "White")
};
}
private void FilterData(string filterPredicate)
{
// Execute live filter
CollectionViewSource.GetDefaultView(this.Persons).Filter =
item => (item as Person).FirstName.StartsWith(filterPredicate, StringComparison.OrdinalIgnoreCase);
}
private string searchPredicate;
public string SearchPredicate
{
get => this.searchFilter;
set
{
this.searchPredicate = value;
FilterData(value);
}
}
public ObservableCollection<Person> Persons { get; set; }
}
MainWindow.xaml
<Window>
<Window.DataContext>
<ViewModel />
</Window.DataContext>
<StackPanel>
<TextBox Text="{Binding SearchPredicate, UpdateSourceTrigger=PropertyChanged"} />
<ListView ItemsSource="{Binding Persons}">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Firstname" DisplayMemberBinding="{Binding FirstName}" />
<GridViewColumn Header="Lastname" DisplayMemberBinding="{Binding LastName}" />
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
</StackPanel>
</Window>
Update
It seems like you are having problems to filter the child items. The following example is more specific to your scenario:
DataItem.cs
class DataItem
{
public DataItem(string Name)
{
this.Name = name;
}
public string Name { get; set; }
public ObservableCollection<DataItem> ChildItems { get; set; }
}
ViewModel.cs
class ViewModel
{
public ViewModel()
{
this.ParentItems = new ObservableCollection<DataItem>()
{
new DataItem("Ben Stiller") { ChildItems = new ObservableCollection<DataItem>() { new DataItem("Zoolander"), new DataItem("Tropical Thunder") }},
new DataItem("Al Pacino") { ChildItems = new ObservableCollection<DataItem>() { new DataItem("Scarface"), new DataItem("The Irishman") }},
new DataItem("Keanu Reeves") { ChildItems = new ObservableCollection<DataItem>() { new DataItem("John Wick"), new DataItem("Matrix") }},
new DataItem("Bryan Cranston") { ChildItems = new ObservableCollection<DataItem>() { new DataItem("Breaking Bad"), new DataItem("Malcolm in the Middle") }}
};
}
private void FilterData(string filterPredicate)
{
// Execute live filter
CollectionViewSource.GetDefaultView(this.SelectedParentItem.ChildItems).Filter =
item => (item as DataItem).Name.StartsWith(filterPredicate, StringComparison.OrdinalIgnoreCase);
}
private string searchPredicate;
public string SearchPredicate
{
get => this.searchFilter;
set
{
this.searchPredicate = value;
FilterData(value);
}
}
public ObservableCollection<DataItem> ParentItems { get; set; }
public DataItem SelectedParentItem { get; set; }
}
MainWindow.xaml
<Window>
<Window.DataContext>
<ViewModel />
</Window.DataContext>
<StackPanel>
<ListView ItemsSource="{Binding ParentItems}"
SelectedItem="{Binding SelectedParentItem}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<TextBox Text="{Binding SearchPredicate, UpdateSourceTrigger=PropertyChanged}" />
<ListView ItemsSource="{Binding SelectedParentItem.ChildItems}" />
</StackPanel>
</Window>
Using the examples from #BionicCode- I added a SelectedParentItem INPC property to the ViewModel, performed the filtering on that via CollectionViewSource.Filter, and bound the ChildItems ListView to SelectedParentItem.ChildItems.
I did not bind the text box property changed to a backing field in the VM as per #BionicCode's example, as the "real" ChildItems might be in the mid 10,000's and I didn't want it to filter on each keystroke. So this answer implements the filter button and text box command, and the CanFilterChildren is doing a proper null check.
MainWindowViewModel.cs:
public class MainWindowViewModel : ViewModelBase
{
public ObservableCollection<ParentItem> ParentItems { get; set; }
private ParentItem _selectedParentItem;
public ParentItem SelectedParentItem
{
get { return _selectedParentItem; }
set { SetProperty(ref _selectedParentItem, value); }
}
public MainWindowViewModel()
{
ParentItems = new ObservableCollection<ParentItem>();
LoadDummyParentItems();
}
private ICommand _filterChildrenCommand;
public ICommand FilterChildrenCommand => _filterChildrenCommand ?? (_filterChildrenCommand = new RelayCommand(param => FilterChildren((string)param), param => CanFilterChildren((string)param)));
private bool CanFilterChildren(string filter) => SelectedParentItem != null && filter.Length > 0;
private void FilterChildren(string filter)
{
CollectionViewSource.GetDefaultView(SelectedParentItem.ChildItems).Filter = item => (item as string).Contains(filter);
}
private void LoadDummyParentItems()
{
for (var i = 0; i < 20; i++)
{
ParentItems.Add(new ParentItem()
{
Name = $"Parent Item {i}",
CreatedOn = DateTime.Now.AddHours(i),
IsActive = i % 2 == 0 ? true : false,
Status = i % 2 == 0 ? ParentItemStatus.Status_Two : ParentItemStatus.Status_One,
ChildItems = new List<string>() { $"Child one_{i}", $"Child two_{i}", $"Child three_{i}", $"Child four_{i}" }
});
}
}
}
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
private readonly MainWindowViewModel _viewModel;
public MainWindow()
{
InitializeComponent();
_viewModel = new MainWindowViewModel();
this.DataContext = _viewModel;
}
}
MainWindow.xaml:
<Window x:Class="FilteringDemo.Views.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:FilteringDemo.Views"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".25*"/>
<ColumnDefinition Width=".75*"/>
</Grid.ColumnDefinitions>
<ListView x:Name="ItemList" Grid.Column="0" Margin="2" ItemsSource="{Binding ParentItems}" SelectedItem="{Binding SelectedParentItem, Mode=OneWayToSource}" SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding ElementName=ItemList, Path=SelectedItem.Name}" Margin="2"/>
<TextBlock Grid.Column="1" Text="{Binding ElementName=ItemList, Path=SelectedItem.CreatedOn}" Margin="2"/>
<TextBlock Grid.Column="2" Text="{Binding ElementName=ItemList, Path=SelectedItem.IsActive}" Margin="2"/>
<TextBlock Grid.Column="3" Text="{Binding ElementName=ItemList, Path=SelectedItem.Status}" Margin="2"/>
</Grid>
<ListView Grid.Row="1" Margin="2" ItemsSource="{Binding SelectedParentItem.ChildItems}" />
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="ChildFilterInput" Grid.Column="0" Margin="2">
<TextBox.InputBindings>
<KeyBinding Command="{Binding FilterChildrenCommand}" CommandParameter="{Binding ElementName=ChildFilterInput, Path=Text}" Key="Return" />
</TextBox.InputBindings>
</TextBox>
<Button Grid.Column="1" Content="Filter" Width="100" Margin="2" Command="{Binding FilterChildrenCommand}" CommandParameter="{Binding ElementName=ChildFilterInput, Path=Text}"/>
</Grid>
</Grid>
</Grid>
</Window>
ViewModelBase.cs:
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T field, T newValue, [CallerMemberName] string propertyName = null)
{
if (!EqualityComparer<T>.Default.Equals(field, newValue))
{
field = newValue;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
return true;
}
return false;
}
}
RelayCommand.cs:
public class RelayCommand : ICommand
{
private Predicate<object> _canExecuteMethod;
private Action<object> _executeMethod;
public RelayCommand(Action<object> executeMethod, Predicate<object> canExecuteMethod = null)
{
_executeMethod = executeMethod;
_canExecuteMethod = canExecuteMethod;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return _canExecuteMethod == null ? true : _canExecuteMethod(parameter);
}
public void Execute(object parameter)
{
_executeMethod(parameter);
}
}
ParentItem.cs:
public class ParentItem
{
public string Name { get; set; }
public List<string> ChildItems { get; set; }
public DateTime CreatedOn { get; set; }
public bool IsActive { get; set; }
public ParentItemStatus Status { get; set; }
}
public enum ParentItemStatus
{
Status_One,
Status_Two
}
App.xaml:
<Application x:Class="FilteringDemo.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:FilteringDemo"
StartupUri="Views/MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
Note: The MainWindow.xaml file was moved into a "Views" folder, so I'm including the App.xaml with the updated StartupUri in case anyone is trying to copy and paste.

WPF View not binding unless I switch view before

I'm trying to dev a WPF app which displays 3 views that we can switch using a side menu.
The first View displays articles. I've tried this view in another project on its own and it works just fine.
My issue:
The problem is that when I use it in my navigation (with the exact same code) the Images or Titles won't appear unless I click on another view first then click back.
Here's my current code:
Model: (Article)
public class Article : INotifyPropertyChanged
{
int id;
string title;
string link;
BitmapImage image;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public Article(int id, string title, string link, BitmapImage image)
{
Id = id;
Title = title;
Link = link;
Image = image;
}
public int Id
{
get => id;
set { if (value != id) { id = value; NotifyPropertyChanged(); } }
}
public string Title
{
get => title;
set { if (value != title) { title = value; NotifyPropertyChanged(); } }
}
public string Link
{
get => link;
set { if (value != link) { link = value; NotifyPropertyChanged(); } }
}
public BitmapImage Image
{
get => image;
set { if (value != image) { image = value; NotifyPropertyChanged(); } }
}
}
ViewModel: (ArticlesViewModel)
public class ArticlesViewModel : INotifyPropertyChanged
{
Article[] articles;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public AccueilViewModel()
{
loadArticle();
}
public Article[] Articles
{
get => articles;
set { if (value != articles) { articles = value; NotifyPropertyChanged(); } }
}
private async void loadArticle()
{
var client = new WordPressClient(App.WordpressLink);
try
{
var asychArticles = GetArticles(client);
await asychArticles;
var articles = asychArticles.Result;
int i = 0;
int a;
foreach (WordPressPCL.Models.Post p in articles)
{
a = p.FeaturedMedia.Value;
var media = GetMedia(client, a);
await media;
BitmapImage bimage = App.getImage(media.Result.SourceUrl);
articleImg[i].Source = bimage;
articleTitle[i].Content = p.Title.Rendered.ToUpper();
articleLink[i] = p.Link;
i++;
if (i == 4) break;
}
}
catch (System.Net.Http.HttpRequestException e)
{
}
}
static async Task<IEnumerable<WordPressPCL.Models.Post>> GetArticles(WordPressClient client)
{
var posts = await client.Posts.GetAll();
return posts;
}
static async Task<WordPressPCL.Models.MediaItem> GetMedia(WordPressClient client, int number)
{
var media = await client.Media.GetByID(number);
return media;
}
}
View: (ArticlesView)
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="10*"/>
<RowDefinition Height="5*"/>
</Grid.RowDefinitions>
<Grid Name="Article1" Grid.Row="0" Margin="10,10,10,10" DataContext="{Binding Articles[0]}">
<Grid.RowDefinitions>
<RowDefinition Height="5*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Image Name="Image1" Grid.Row="0" Stretch="UniformToFill" Source="{Binding Image}"/>
<Button Name="Titre1" Grid.Row="1" Content="{Binding Title}"/>
</Grid>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid Name="Article2" Grid.Column="0" Margin="10,10,10,10" DataContext="{Binding Articles[1]}">
<Grid.RowDefinitions>
<RowDefinition Height="5*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Image Name="Image2" Grid.Row="0" Stretch="UniformToFill" Source="{Binding Image}"/>
<Button Name="Titre2" Grid.Row="1" Content="{Binding Title}"/>
</Grid>
<Grid Name="Article3" Grid.Column="1" Margin="10,10,10,10" DataContext="{Binding Articles[2]}">
<Grid.RowDefinitions>
<RowDefinition Height="5*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Image Name="Image3" Grid.Row="0" Stretch="UniformToFill" Source="{Binding Image}"/>
<Button Name="Titre3" Grid.Row="1" Content="{Binding Title}"/>
</Grid>
<Grid Name="Article4" Grid.Column="2" Margin="10,10,10,10" DataContext="{Binding Articles[3]}">
<Grid.RowDefinitions>
<RowDefinition Height="5*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Image Name="Image4" Grid.Row="0" Stretch="UniformToFill" Source="{Binding Image}"/>
<Button Name="Titre4" Grid.Row="1" Content="{Binding Title}"/>
</Grid>
</Grid>
</Grid>
MainWindow.xaml:
<Window.Resources>
<DataTemplate x:Name="ArticlesViewTemplate" DataType="{x:Type viewmodels:ArticlesViewModel}">
<views:ArticlesView DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate x:Name="Feature2ViewTemplate" DataType="{x:Type viewmodels:Feature2ViewModel}">
<views:Feature2View DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate x:Name="Feature3ViewTemplate" DataType="{x:Type viewmodels:Feature3ViewModel}">
<views:Feature3View DataContext="{Binding}"/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="4*" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<StackPanel Grid.Row="1" VerticalAlignment="Top" Margin="20,20,20,0">
<Button Content="Articles" Click="F1Button_Click"/>
<Button Content="F2" Click="F2Button_Click"/>
<Button Content="F3" Click="F3Button_Click"/>
</StackPanel>
</Grid>
<ContentControl Grid.Column="1" Content="{Binding}"/>
</Grid>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
ArticlesViewModel avm;
Feature2ViewModel f2vm;
Feature3ViewModel f3vm;
public MainWindow()
{
InitializeComponent();
}
private void F1Button_Click(object sender, RoutedEventArgs e)
{
if (avm == null) avm = new AccueilViewModel();
DataContext = avm;
}
private void F2Button_Click(object sender, RoutedEventArgs e)
{
if (f2vm == null) f2vm = new CatalogueViewModel();
DataContext = f2vm;
}
private void F3Button_Click(object sender, RoutedEventArgs e)
{
if (f3vm == null) f3vm = new MesFormationsViewModel();
DataContext = f3vm;
}
}
To sum up:
Click on Articles -> Nothing is displayed
Click on F2 then Articles -> Nothing is displayed
Click on Articles then F2 then Articles -> Images and Titles correctly displayed!
Any ideas on where the problem might be coming from? Thank you.
So the problem seemed to be coming from the fact that I was using an array. At the start the DataContext was null so the images and titles even though they were loaded in the modelview were not displayed on the view.
The problem was fixed by using an ObservableCollection instead of the array. If I understood correctly, NotifyPropertyChanged() is apparently not notifying the view when the array is at first null.(?)

How to handle item highlight in ListBox when using more than one ListBox using wpf c#

I am using more than one customise ListBox in my application.
I want that when I click on item in one of the ListBox then highlight in other ListBox should be remove .Means I want to select item in one of the ListBox at a time.I provide sample code here
MainWindow.xaml
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<view:ListBox_UserControl x:Name="Control_1"/>
</Grid>
<Grid Grid.Row="1">
<view:ListBox_UserControl x:Name="Control_2"/>
</Grid>
<Grid Grid.Row="2">
<view:ListBox_UserControl x:Name="Control_3"/>
</Grid>
</Grid>
<Grid Grid.Column="1" Background="Aqua">
</Grid>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Control_1.ListBox_ItemSource = List_1;
Control_2.ListBox_ItemSource = List_1;
Control_3.ListBox_ItemSource = List_1;
}
List<string> List_1 = new List<string>()
{
"Item_1",
"Item_2",
"Item_3",
"Item_4",
"Item_5"
};
}
ListBox_UserControl.xaml
<Grid>
<ListBox Name="ListBox_1">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding }" MouseLeftButtonDown="TextBlock_MouseLeftButtonDown"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
ListBox_UserControl.xaml.cs
public partial class ListBox_UserControl : UserControl
{
protected event MouseButtonEventHandler TextBlock_Click;
public ListBox_UserControl()
{
InitializeComponent();
}
public List<string> ListBox_ItemSource
{
set
{
ListBox_1.ItemsSource = value;
}
}
private void TextBlock_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
MouseButtonEventHandler handler = TextBlock_Click;
if(handler!=null)
{
handler(sender, e);
}
}
}

Categories