WPF MVVM and Observablecollect - wpf

I have a wpf application and I want to update my listview when I change the value through the UI using Observablecollect. But I don't get what I expect. When I change the value I won't update my list view.
View Code(Xaml)
<UserControl x:Class="DataWatch.View.CompareData"
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:ViewModels="clr-namespace:DataWatch.ViewModel"
mc:Ignorable="d"
d:DesignHeight="340" d:DesignWidth="600">
<UserControl.DataContext>
<ViewModels:CompareViewModel/>
</UserControl.DataContext>
<Grid>
<ListView HorizontalAlignment="Left" Name="comparelistview" VerticalAlignment="Top" Width="600" Height="340" ItemsSource="{Binding DisplayData}">
<ListView.View>
<GridView>
<GridViewColumn Width="150" Header="Key"
DisplayMemberBinding="{Binding Path=Key}" />
<GridViewColumn Width="150" Header="Project Data"
DisplayMemberBinding="{Binding Path=ProjectData}" />
<GridViewColumn Width="150" Header="Import Data"
DisplayMemberBinding="{Binding Path=ImportData}"/>
<GridViewColumn Width="150" Header="State"
DisplayMemberBinding="{Binding Path=State}"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
ViewModel
namespace DataWatch.ViewModel
{
public class CompareViewModel
{
private ObservableCollection<CompareDiplayData> _displayData;
public CompareViewModel()
{
_displayData = new ObservableCollection<CompareDiplayData>();
}
public ObservableCollection<CompareDiplayData> DisplayData
{
get { return _displayData; }
}
}
Model:
namespace DataWatch.Model
{
public class CompareDiplayData : INotifyPropertyChanged
{
private string _key;
public string Key
{
set
{
_key = value;
this.Changed("Key");
}
get
{
return _key;
}
}
private string _projectData;
public string ProjectData
{
set
{
_projectData = value;
this.Changed("ProjectData");
}
get
{
return _projectData;
}
}
private string _importData;
public string ImportData
{
set
{
_importData = value;
this.Changed("ImportData");
}
get
{
return _importData;
}
}
private string _state;
public string State
{
set
{
_state = value;
this.Changed("State");
}
get
{
return _state;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void Changed(string PropertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
}
When I change the value in displayData ,but Listview won't update the data.
combobox view control
<UserControl x:Class="DataWatch.View.SelectPanel"
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:ViewModels="clr-namespace:DataWatch.ViewModel"
xmlns:AttachProperty="clr-namespace:DataWatch.AttachedProperty"
mc:Ignorable="d"
d:DesignHeight="30" d:DesignWidth="600">
<UserControl.DataContext>
<ViewModels:CompareViewModel/>
</UserControl.DataContext>
<Grid Name="good">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150">
</ColumnDefinition>
<ColumnDefinition Width="300">
</ColumnDefinition>
<ColumnDefinition Width="150">
</ColumnDefinition>
</Grid.ColumnDefinitions>
<ComboBox Height="23" HorizontalAlignment="Left" Grid.Column="0" Grid.Row="0" Margin="15,4,6,4" x:Name="KeyComboBox" VerticalAlignment="Top" Width="120" Text="Choose Key" AttachProperty:SelectionBehavior.SelectionChanged="{Binding SelectKeyCmd}" SelectedItem="{Binding SelectedKey, Mode=TwoWay}" ItemsSource="{Binding KeyComboboxItem}"
IsEditable="true" IsReadOnly="true"
IsDropDownOpen="True" StaysOpenOnEdit="True"/>
<ComboBox Height="23" HorizontalAlignment="Left" Grid.Column="1" Grid.Row="0" Margin="90,4,6,4" Name="compareitemcomBox" VerticalAlignment="Top" Width="120" Text="Compare Item" AttachProperty:SelectionBehavior.SelectionChanged="{Binding SelectKeyCmd}" SelectedItem="{Binding SelectedComparedData, Mode=TwoWay}" ItemsSource="{Binding CompareComboboxItem}"
IsEditable="true" IsReadOnly="true"
IsDropDownOpen="True" StaysOpenOnEdit="True"/>
<ComboBox Height="23" HorizontalAlignment="Left" Grid.Column="2" Grid.Row="0" Margin="15,4,6,4" Name="display" VerticalAlignment="Top" Width="120" Text="Choose State"
IsEditable="true" IsReadOnly="true"
IsDropDownOpen="True" StaysOpenOnEdit="True"/>
</Grid>
Attach property
public class SelectionBehavior
{
public static DependencyProperty SelectionChangedProperty =
DependencyProperty.RegisterAttached("SelectionChanged",
typeof(ICommand),
typeof(SelectionBehavior),
new UIPropertyMetadata(SelectionBehavior.SelectedItemChanged));
public static void SetSelectionChanged(DependencyObject target, ICommand value)
{
target.SetValue(SelectionBehavior.SelectionChangedProperty, value);
}
private static void SelectedItemChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
Selector element = target as Selector;
if (element == null) throw new InvalidOperationException("This behavior can be attached to Selector item only.");
if ((e.NewValue != null) && (e.OldValue == null))
{
element.SelectionChanged += SelectionChanged;
}
else if ((e.NewValue == null) && (e.OldValue != null))
{
element.SelectionChanged -= SelectionChanged;
}
}
private static void SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
UIElement element = (UIElement)sender;
ICommand command = (ICommand)element.GetValue(SelectionBehavior.SelectionChangedProperty);
command.Execute(((Selector)sender).SelectedValue);
}
}
viewmodel
public class CompareViewModel
{
private readonly ICommand _selectKeyCmd;
private List<string> _pro_Property;
private List<string> _imp_Property;
private string selectedKey;
private ObservableCollection<string> compareComboboxItem;
private DataTable _dt;
private CompareDiplayData domObject;
private ObservableCollection<CompareDiplayData> _displayData;
public CompareViewModel()
{
_displayData = new ObservableCollection<CompareDiplayData>();
_selectKeyCmd = new RelayCommand(ComboboxChanged, ComboboxIsChanged);
}
private void ReslutData()
{_displayData =null;
if (SelectedKey != null && SelectedComparedData != null)
{
foreach (DataRow pdr in _projectDt.Rows)
{
CompareDiplayData cdd = new CompareDiplayData();
foreach (DataRow idr in _importDt.Rows)
{
if (pdr[SelectedKey].ToString() == idr[SelectedKey].ToString())
{
cdd.Key = pdr[SelectedKey].ToString();
cdd.ProjectData = pdr[SelectedComparedData].ToString();
cdd.ImportData = idr[SelectedComparedData].ToString();
if (pdr[SelectedComparedData].ToString() == idr[SelectedComparedData].ToString())
cdd.State = "Match";
else
cdd.State = "Mismatch";
_displayData.Add(cdd);
}
}
}
}
}
public ObservableCollection<CompareDiplayData> DisplayData
{
get { return _displayData; }
}
public ICommand SelectKeyCmd
{
get { return _selectKeyCmd; }
}
private void ComboboxChanged(object obj)
{
ReslutData();
}
private bool ComboboxIsChanged(object obj)
{
return true;
}`}`

You are updating the reference of the Observablecollection here so that needs to be notified as the PropertyChange. In order to fix this, you will have to implement the INotifyPropertyChanged on your ViewModel also and write the setter of the DisplayData and raise property change for it.
public ObservableCollection<CompareDiplayData> DisplayData
{
get { return _displayData; }
set{_displayData = value;
this.Changed("DisplayData");
}
and in your ReslutData functin instead of update the variable _displayData, update the Property DisplayData.

Related

WPF user control binding dynamic combobox

I have a select list called Printers which is filled by default, after selecting a value from this select list the list called Trays should be filled but it doesn't. I've tried various methods but it seems to me that I still don't understand what the problem is. In addition, I would like to see a new list after pressing the Add next row button and it would also be full. I tried to use DependencyProperty and INotifyPropertyChanged but I don't think I fully understand the basics of the assumption or my problem is specific.
<UserControl x:Class="TestWPF.User_Controls.TrayItem"
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:TestWPF.User_Controls"
mc:Ignorable="d"
Name="ucTray"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="White">
<StackPanel Margin="10" x:Name="spTray" Orientation ="Horizontal" Height="35" HorizontalAlignment="Center">
<Label VerticalAlignment="Center">Trays</Label>
<ComboBox Width="250" VerticalContentAlignment="Center">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ElementName=ucTray, Path=Trays }" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Label VerticalAlignment="Center">Documents</Label>
<ComboBox Width="250" VerticalContentAlignment="Center">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Documents , RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</Grid>
namespace TestWPF.User_Controls
{
public partial class TrayItem : UserControl
{
public List<string> Trays
{
get { return (List<string>)GetValue(TraysProperty); }
set { SetValue(TraysProperty, value); }
}
// Using a DependencyProperty as the backing store for Trays. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TraysProperty =
DependencyProperty.Register("Trays", typeof(List<string>), typeof(TrayItem), new PropertyMetadata(null));
public List<string> Documents
{
get { return (List<string>)GetValue(DocumentsProperty); }
set { SetValue(DocumentsProperty, value); }
}
// Using a DependencyProperty as the backing store for Documents. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DocumentsProperty =
DependencyProperty.Register("Documents", typeof(List<string>), typeof(TrayItem), new PropertyMetadata(null));
public TrayItem()
{
InitializeComponent();
}
}
}
<Window x:Class="TestWPF.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:TestWPF"
xmlns:control="clr-namespace:TestWPF.User_Controls"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<StackPanel Margin="10" x:Name="spItems">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<Label>Printers</Label>
<ComboBox x:Name="cmbPrinterList" Width="300" SelectionChanged="cmbPrinterList_SelectionChanged" ItemsSource="{Binding MyPrinters}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
<control:TrayItem x:Name="cTray" Trays="{Binding MyTrays, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Documents="{Binding Path = MyDocuments, Mode=TwoWay}">
</control:TrayItem>
</StackPanel>
<Button x:Name="btnAddTray" Margin="10" Width="100" Height="40" Click="btnAddTray_Click" >Add next row</Button>
</Grid>
</Window>
namespace TestWPF
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
public List<string> MyPrinters { get; set; }
public string? _SelectedPrinter;
public string? SelectedPrinter
{
get
{
return _SelectedPrinter;
}
set
{
_SelectedPrinter = value;
}
}
private List<string>? _MyTrays;
public List<string>? MyTrays
{
get
{
return _MyTrays;
}
set
{
_MyTrays = value;
RaisePropertyChanged("SelectedPrinter");
RaisePropertyChanged("MyTrays");
RaisePropertyChanged("MyDocuments");
}
}
public List<string>? MyDocuments { get; set; }
public MainWindow()
{
InitializeComponent();
DataContext = this;
MyPrinters = new List<string>()
{
"Printer_1",
"Printer_2",
"Printer_3"
};
}
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler? PropertyChanged;
private void cmbPrinterList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
string? value = (string?)cmbPrinterList.SelectedItem;
switch (value)
{
case "Printer_1":
MyTrays = new List<string>()
{
"aaa",
"bbb",
"ccc"
};
MyDocuments = new List<string>()
{
"zzz",
"qqq",
"rrr"
};
break;
case "Printer_2":
MyTrays = new List<string>()
{
"111",
"222",
"333"
};
MyDocuments = new List<string>()
{
"666",
"777",
"888"
};
break;
case "Printer_3":
MyTrays = new List<string>()
{
"1dg",
"b2f",
"bs4"
};
MyDocuments = new List<string>()
{
"b2vg",
"eb5",
"asc"
};
break;
}
SelectedPrinter = value;
}
private void btnAddTray_Click(object sender, RoutedEventArgs e)
{
TrayItem trayItem = new TrayItem { DataContext = cTray.DataContext };
spItems.Children.Add(trayItem);
}
}
}

WPF ListView - column header not as property, but as Key of Dictionary (Dictionary is ListViewItem property) - ComparisionMatrixControl

I would like to make a MatrixControl in my WPF app. First thing it starts from ListView and defines 'MatrixLine' in the model. ListView ItemsSource was an ObservableCollection. I have a simple complete model that shows what I'm aiming for and a picture with the result I expect for the created model.
I have a problem with how to add the 'MatrixLine' property type of Dictionary, [Keys] as a columns header in the ListView and [Values] (boolean) as a sign 'x' on te ListView. (The picture with the result that follows)
Expected result for my model
Result for my model creating in ViewModel constructor
GitHub Project
https://github.com/Varran/WPF_Multiporownywarki_Baza
Model
public class ColorBase
{
public string Name { get; }
public int Saturation { get; private set; }
public ColorBase(string name, int saturation)
{
this.Name = name;
this.Saturation = saturation;
}
public void ChangeSaturation(int newSaturation)
{
Saturation = newSaturation;
}
public override string ToString()
{
return $"ColorBase: {Saturation.ToString().PadLeft(4, ' ')} - '{Name}'";
}
}
public class MixedPaint
{
public string PaintName { get; }
public List<ColorBase> Ingredients { get; }
public MixedPaint(string name)
{
Ingredients = new List<ColorBase>();
this.PaintName= name;
}
public MixedPaint AddIngredient(ColorBase color)
{
bool added = false;
foreach (var item in Ingredients)
{
if (item.Name == color.Name )
{
item.ChangeSaturation(item.Saturation + color.Saturation);
added = true;
}
}
if (!added)
Ingredients.Add(color);
return this;
}
}
public class MatrixLine
{
public ColorBase ColorIngredient { get; private set; }
public Dictionary<string, bool> Matrix;
public MatrixLine(ColorBase color)
{
Matrix = new Dictionary<string, bool>();
this.ColorIngredient = color;
}
public void AddToMatrix(MixedPaint mixedPaint)
{
string paintName = mixedPaint.PaintName;
bool doesItContainIgredient = mixedPaint.Ingredients.Any(o => (o.Name == ColorIngredient.Name &&
o.Saturation == ColorIngredient.Saturation));
Matrix.Add(paintName, doesItContainIgredient);
}
}
ViewModel
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<MixedPaint> mixedPaints;
public ObservableCollection<MixedPaint> MixedPaints { get { return mixedPaints; } }
public event PropertyChangedEventHandler? PropertyChanged;
private void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
private MixedPaint selectedMixedPaint;
public MixedPaint SelectedMixedPaint {
get { return selectedMixedPaint; }
set { selectedMixedPaint = value;
OnPropertyChanged(nameof(SelectedMixedPaint)); } }
private ObservableCollection<MatrixLine> comparisonMatrix;
public ObservableCollection<MatrixLine> ComparisonMatrix { get { return comparisonMatrix; } }
public ViewModel()
{
ColorBase yellowA = new ColorBase("YellowA", 110);
ColorBase yellowB = new ColorBase("YellowB", 175);
ColorBase blueA = new ColorBase("BlueA", 77);
ColorBase blueB = new ColorBase("BlueB", 135);
ColorBase redA = new ColorBase("RedA", 95);
ColorBase redB = new ColorBase("RedB", 225);
ColorBase whiteA = new ColorBase("WhiteA", 200);
MixedPaint greenA = new MixedPaint("GreenLight")
.AddIngredient(yellowA)
.AddIngredient(blueA);
MixedPaint greenB = new MixedPaint("GreenDark")
.AddIngredient(yellowB)
.AddIngredient(blueB);
MixedPaint orangeA = new MixedPaint("OrangeLight")
.AddIngredient(yellowA)
.AddIngredient(redB)
.AddIngredient(whiteA);
MixedPaint orangeB = new MixedPaint("OrangeDark")
.AddIngredient(yellowB)
.AddIngredient(redB);
MixedPaint violet = new MixedPaint("Violet")
.AddIngredient(redA)
.AddIngredient(blueB);
mixedPaints = new ObservableCollection<MixedPaint>() { greenA, greenB, orangeA, orangeB, violet };
SelectedMixedPaint = greenA;
List<ColorBase> uniqueColorsBase = new List<ColorBase>();
foreach (var item in mixedPaints)
foreach (var item2 in item.Ingredients)
if (!uniqueColorsBase.Contains(item2))
uniqueColorsBase.Add(item2);
uniqueColorsBase = uniqueColorsBase.OrderBy(o => o.Name).ThenBy(o => o.Saturation).ToList();
comparisonMatrix = new ObservableCollection<MatrixLine>();
foreach (var color in uniqueColorsBase)
{
MatrixLine line = new MatrixLine(color);
foreach (var mixed in mixedPaints)
line.AddToMatrix(mixed);
comparisonMatrix.Add(line);
}
}
}
View
<Window x:Class="WPF_multi_próby.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:WPF_multi_próby"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="150"/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Name="ListOfMixedPaint"
Grid.Row="0" Grid.Column="0" Orientation="Vertical">
<TextBlock Text="List of MixedPaint:"/>
<ListView ItemsSource="{Binding MixedPaints}" SelectedItem="{Binding SelectedMixedPaint}" Margin="10">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="PaintName: "/>
<TextBlock Text="{Binding PaintName}" Width="120" FontWeight="Bold"/>
<TextBlock Text="IngradientCount: " Margin="0,0,10,0"/>
<TextBlock Text="{Binding Ingredients.Count}" FontWeight="Bold"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
<StackPanel Name="BOM"
Grid.Column="1" Grid.Row="0">
<TextBlock Text="Ingredients of selected MixedPaint"/>
<ListView ItemsSource="{Binding SelectedMixedPaint.Ingredients}" Margin="10">
<ListView.View>
<GridView>
<GridViewColumn Header="Color Name" DisplayMemberBinding="{Binding Name}" Width="100"/>
<GridViewColumn Header="Color Saturation" DisplayMemberBinding="{Binding Saturation}" Width="100"/>
</GridView>
</ListView.View>
</ListView>
</StackPanel>
<StackPanel Name="MultiComparerOfPaints"
Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2" Orientation="Vertical">
<TextBlock Text="Multicomparer of paints"/>
<ListView ItemsSource="{Binding ComparisonMatrix}" Margin="10" FontFamily="Cascadia Code" >
<ListView.View>
<GridView>
<GridViewColumn Header="Unique ingredient" DisplayMemberBinding="{Binding ColorIngredient}" Width="180"/>
<!-- no idea how binding -->
</GridView>
</ListView.View>
</ListView>
</StackPanel>
</Grid>
</Window>
I think this is my solution.
I do not fully understand this solution yet, but I will have to try.
DataMatrix in WPF - codeproject
or
Binding matrix arrays to WPF DataGrid

Command not executing when clicking a button

How should the collection be bound to Listview and have the button execute the command to add a single user to the collection.
View Model Code:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public class UserViewModel : ViewModelBase
{
private ObservableCollection<User> _UsersList;
private User _user;
public UserViewModel()
{
_user = new User();
_UsersList = new ObservableCollection<User>();
_UsersList.CollectionChanged += _UsersList_CollectionChanged;
}
private void _UsersList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
}
public ObservableCollection<User> Users
{
get { return _UsersList; }
set
{
_UsersList = value;
OnPropertyChanged("Users");
}
}
public User User
{
get
{
return _user;
}
set
{
_user = value;
OnPropertyChanged("User");
}
}
private ICommand mUpdater;
public ICommand UpdateCommand
{
get
{
if (mUpdater == null)
mUpdater = new Updater(this);
return mUpdater;
}
//set
//{
// mUpdater = value;
//}
}
private void Submit()
{
Users.Add(User);
User = new User();
}
private class Updater : ICommand
{
#region ICommand Members
UserViewModel _obj;
public Updater(UserViewModel obj)
{
_obj = obj;
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_obj.Submit();
}
}
View Xaml:
<ListView Name="UserGrid"
Grid.Row="1"
Margin="4,178,12,13"
ItemsSource="{Binding Users}" >
<ListView.View>
<GridView x:Name="grdTest">
<GridViewColumn Header="UserId"
DisplayMemberBinding="{Binding UserId}"
Width="50"/>
</GridView>
</ListView.View>
</ListView>
<TextBlock Grid.Row="0"
Grid.Column="0"
Text="Name"
HorizontalAlignment="Center"/>
<TextBox Grid.Row="0"
Grid.Column="1"
Width="100"
HorizontalAlignment="Center"
Text="{Binding User.UserId, Mode=TwoWay}"/>
<Button Content="Update"
Grid.Row="1"
Height="23"
HorizontalAlignment="Left"
Margin="310,40,0,0"
Name="btnUpdate"
VerticalAlignment="Top"
Width="141"
Command="{Binding Path=UpdateCommad}" />
The error is in the binding of the button to the command:
Command="{Binding Path=UpdateCommad}" />
It should be:
Command="{Binding Path=UpdateCommand}" />
To find errors like this always read the Debug Output. In this case it quickly showed:
System.Windows.Data Error: 40 : BindingExpression path error:
'UpdateCommad' property not found on 'object' ''UserViewModel'
To prevent this from happening at all you could use Xaml code completion by setting the design-time DataContext type:
<Window x:Class="TestWpfApplication.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:TestWpfApplication"
mc:Ignorable="d"
Title="MainWindow"
Height="350"
Width="525"
d:DataContext="{d:DesignInstance Type=local:UserViewModel}">
After setting this you will have code-completion in Xaml: just start typing the binding path and it will offer valid options.

How to Scroll to a new item added to a list view in MVVM Light

I've got a project where there is a ListView and when the User clicks the New button the new Object is added to the bottom of the ListView. I've tried using a Content Style class but that didn't work. I just need something that will scroll to the selected item. Below is my code:
View:
<ListView Margin="103,0,0,10" ScrollViewer.CanContentScroll="True" Height="87" SelectedItem="{Binding SelectedSession, Mode=TwoWay}" ItemsSource="{Binding SessionCollection}">
<ListView.Resources>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="HorizontalContentAlignment" Value="Left" />
</Style>
</ListView.Resources>
<ListView.View>
<GridView>
<GridViewColumn Header="Session Name" Width="180" DisplayMemberBinding="{Binding SessionName, Mode=TwoWay}" />
<GridViewColumn Header="Operator Name" Width="180" DisplayMemberBinding="{Binding OperatorName, Mode=TwoWay}"/>
<GridViewColumn Header="Session Date" Width="180" DisplayMemberBinding="{Binding SessionDate, Mode=TwoWay}"/>
</GridView>
</ListView.View>
</ListView>
View Model code for the New Button :
public void NewSession()
{
Session newSession = new Session();
SessionCollection.Add(newSession);
SelectedSession = newSession;
SessionDate = DateTime.Now;
StartTime = DateTime.Now;
EndTime = DateTime.Now;
ProjectManager.Instance.CurrentSession = null;
}
public ObservableCollection<Session> SessionCollection
{
get
{
if (currentDatabaseProj.Sessions == null)
{
return currentDatabaseProj.Sessions;
}
else
{
return currentDatabaseProj.Sessions;
}
}
private set
{
currentDatabaseProj.Sessions = value;
IsNewProjClicked = true;
this.RaisePropertyChanged("SessionCollection");
}
}
One simple handler in the code behind should do the trick
(i simplify your code to made it clear)
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListView x:Name="listView" Margin="10" ScrollViewer.CanContentScroll="True" SelectedItem="{Binding SelectedSession, Mode=TwoWay}" ItemsSource="{Binding SessionCollection}">
<ListView.Resources>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="HorizontalContentAlignment" Value="Left" />
</Style>
</ListView.Resources>
<ListView.View>
<GridView>
<GridViewColumn Header="Session Name" Width="180" DisplayMemberBinding="{Binding}" />
</GridView>
</ListView.View>
</ListView>
<Button Click="UIElement_NewElementHandler" Grid.Row="1" Content="Add Item" Command="{Binding AddItem}"></Button>
</Grid>
and in the view's code behind add the following handler :
private void UIElement_NewElementHandler(object sender, RoutedEventArgs routedEventArgs)
{
var border = VisualTreeHelper.GetChild(listView, 0) as Decorator;
var scrollViewer = border.Child as ScrollViewer;
scrollViewer.ScrollToBottom();
}
ps: scrolling down to a new added item is something ViewRelated, none of the View Model Properties are used in the codebehind, so i believe you are not violating any mvvm rule by doing that.
Working example with behavior
xaml
<Window x:Class="ListView.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:listView="clr-namespace:ListView"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button Content="Add New" Grid.Row="0" Click="Button_Click"></Button>
<ListView Grid.Row="1" ItemsSource="{Binding MySessionList}" SelectedItem="{Binding SelectedSession, Mode=TwoWay}">
<i:Interaction.Behaviors>
<listView:ScrollIntoViewBehavior/>
</i:Interaction.Behaviors>
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="200" DisplayMemberBinding="{Binding Name, Mode=TwoWay}" />
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
cs
public partial class MainWindow : Window
{
private Viewmodel _data;
public MainWindow()
{
InitializeComponent();
_data = new Viewmodel();
this.DataContext = _data;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
_data.AddNew();
}
}
public class Viewmodel : INotifyPropertyChanged
{
private MySession _selectedSession;
public ObservableCollection<MySession> MySessionList { get; set; }
public Viewmodel()
{
this.MySessionList = new ObservableCollection<MySession>();
//fill with some data
for (int i = 0; i < 20; i++)
{
this.MySessionList.Add(new MySession(){Name = "Index : " + i});
}
}
public MySession SelectedSession
{
get { return _selectedSession; }
set
{
_selectedSession = value;
OnPropertyChanged();
}
}
//should be a Command for MVVM but for quick and dirty
public void AddNew()
{
var toAdd = new MySession() {Name = "New Added " + DateTime.Now.ToLongTimeString()};
this.MySessionList.Add(toAdd);
this.SelectedSession = toAdd;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class MySession
{
public string Name { get; set; }
}
//ADD REFERENCE: System.Windows.Interactivity
public class ScrollIntoViewBehavior : Behavior<ListBox>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectionChanged += AssociatedObject_SelectionChanged;
}
private void AssociatedObject_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var ctrl = sender as ListBox;
if (ctrl == null)
return;
if (ctrl.SelectedItem != null)
{
ctrl.Dispatcher.BeginInvoke(
DispatcherPriority.Render,
new Action(() =>
{
ctrl.UpdateLayout();
ctrl.ScrollIntoView(ctrl.SelectedItem);
}));
}
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.SelectionChanged -= AssociatedObject_SelectionChanged;
}
}

WPF simple binding problem

Trying to understand this binding process of the WPF.
See the code at the bottom.
In my "viewmodel", see the code at the bottom, i have an observable collection that is populating the listview with the items. Thats the one that contains a path called symbol to set the selected index in the combobox. Now my problem is that i need to populate the combobox from another list before its added to the listview (some default values).
Since i just started with WPF i thought that perhaps you can use 2 different ObservableCollections in the same class to achieve this but that didn't work. So how can i populate the datatemplate with the default values before they are bound/added to the listview?
This is what i use to populate the listview, see the viewmodelcontacts at the bottom.
I also tried to add another class with a new observablecollection that i could use in my combobox, but i didn't get that to work either.
The data that should be populated comes from a XML file located as a resource in my app.
Another question, is it possible to add commands to images? or are commands only available from controls that inherit from the button_base class? I wanted to use an image next to an element and when the user clicked on that image they would remove the element.
From the answer below, is it possible without adding a button since i don't want the button feeling (for instance when hovering and clicking)*
Window.xaml:
<r:RibbonWindow x:Class="Onyxia_KD.Windows.ContactWorkspace"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:r="clr-namespace:Microsoft.Windows.Controls.Ribbon;assembly=RibbonControlsLibrary"
Title="Contact card" ResizeMode="NoResize" Height="600" Width="600"
Background="White">
<r:RibbonWindow.Resources>
<DataTemplate x:Key="cardDetailFieldTemplate">
<TextBox Text="{Binding Path=Field}" MinWidth="150"></TextBox>
</DataTemplate>
<DataTemplate x:Key="cardDetailValueTemplate">
<TextBox Text="{Binding Path=Value}" MinWidth="150"></TextBox>
</DataTemplate>
<DataTemplate x:Key="cardDetailSymbolTemplate">
<!-- Here is the problem. Populating this with some default values for each entry before the selectedIndex is bound from the datasource -->
<ComboBox SelectedIndex="{Binding Path=Symbol}" DataContext="_vmSettings" ItemsSource="{Binding Symbols}" Grid.Column="1" Margin="0,0,10,0" VerticalAlignment="Center" HorizontalAlignment="Center">
<ListViewItem Padding="0,3,0,3" Content="{Binding Path=Value}" />
</ComboBox>
</DataTemplate>
<DataTemplate x:Key="cardDetailCategoryTemplate">
<ComboBox SelectedIndex="{Binding Path=Category}" Grid.Column="1" Margin="0,0,10,0" VerticalAlignment="Center" HorizontalAlignment="Center">
<!--same as the combobox above but categories instead of symbols-->
</ComboBox>
</DataTemplate>
</r:RibbonWindow.Resources>
...
<ListView ItemsSource="{Binding ContactData}" Foreground="Black" SelectionMode="Single" x:Name="cardDetailList" KeyDown="cardDetailList_KeyDown">
<ListView.View>
<GridView>
<GridViewColumn Header="Category" Width="auto" CellTemplate="{StaticResource cardDetailCategoryTemplate}"></GridViewColumn>
<GridViewColumn Header="Field" Width="auto" CellTemplate="{StaticResource cardDetailFieldTemplate}"></GridViewColumn>
<GridViewColumn Header="Symbol" Width="70" CellTemplate="{StaticResource cardDetailSymbolTemplate}"></GridViewColumn>
<GridViewColumn Header="Value" Width="auto" CellTemplate="{StaticResource cardDetailValueTemplate}"></GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Code behind:
private ViewModelContacts _vm;
public ContactWorkspace()
{
InitializeComponent();
_vm = new ViewModelContacts();
this.DataContext = _vm;
}
private void Run_MouseUp(object sender, MouseButtonEventArgs e)
{
_vm.AddNewDetail();
}
private void Image_MouseUp(object sender, MouseButtonEventArgs e)
{
_vm.AddNewDetail();
}
private void cardDetailList_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Delete)
{
if (MessageBox.Show("Are you sure that you want to delete this?", "Warning!", MessageBoxButton.YesNo, MessageBoxImage.Warning) == MessageBoxResult.Yes)
{
_vm.RemoveDetail(cardDetailList.SelectedIndex);
}
}
}
ViewModelContacts:
public ObservableCollection<ContactCardData> ContactData { get; set; }
public ViewModelContacts()
{
ContactData = new ObservableCollection<ContactCardData>();
Populate();
}
private void Populate()
{
ContactData.Add(new ContactCardData("Test", 0, 0, "Value123"));
ContactData.Add(new ContactCardData("Test2", 1, 1, "Value1234"));
ContactData.Add(new ContactCardData("Test3", 2, 2, "Value1235"));
ContactData.Add(new ContactCardData("Test4", 3, 3, "Value12356"));
}
public void UpdateNode()
{
ContactData.ElementAt(0).Value = "Giraff";
}
public void AddNewDetail()
{
ContactData.Add(new ContactCardData());
}
public void RemoveDetail(int position)
{
ContactData.RemoveAt(position);
}
ViewModelContactData:
public class ContactCardData : DependencyObject
{
public int Category
{
get { return (int)GetValue(CategoryProperty); }
set { SetValue(CategoryProperty, value); }
}
// Using a DependencyProperty as the backing store for Category. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CategoryProperty =
DependencyProperty.Register("Category", typeof(int), typeof(ContactCardData), new UIPropertyMetadata(0));
public string Field
{
get { return (string)GetValue(FieldProperty); }
set { SetValue(FieldProperty, value); }
}
// Using a DependencyProperty as the backing store for Field. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FieldProperty =
DependencyProperty.Register("Field", typeof(string), typeof(ContactCardData), new UIPropertyMetadata(""));
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(string), typeof(ContactCardData), new UIPropertyMetadata(""));
public int Symbol
{
get { return (int)GetValue(SymbolProperty); }
set { SetValue(SymbolProperty, value); }
}
// Using a DependencyProperty as the backing store for Symbol. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SymbolProperty =
DependencyProperty.Register("Symbol", typeof(int), typeof(ContactCardData), new UIPropertyMetadata(0));
public ContactCardData()
{
}
public ContactCardData(string field, int category, int symbol, string value)
{
this.Symbol = symbol;
this.Category = category;
this.Field = field;
this.Value = value;
}
}
Define your Window class as follows (explanation will be later):
<Window x:Class="TestCustomTab.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:TestCustomTab="clr-namespace:TestCustomTab" Title="Window1" Height="300" Width="300">
<Window.Resources>
<DataTemplate x:Key="workingTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Name}"/>
<ComboBox Grid.Column="1" SelectedIndex="0" ItemsSource="{Binding Symbols}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0" Fill="Red" Height="5" Width="5"/>
<TextBlock Grid.Column="1" Text="{Binding}" />
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</DataTemplate>
<DataTemplate x:Key="personalTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Name}"/>
<ComboBox Grid.Column="1" SelectedIndex="0" ItemsSource="{Binding Symbols}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0" Fill="Green" Height="5" Width="5"/>
<TextBlock Grid.Column="1" Text="{Binding}" />
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</DataTemplate>
<TestCustomTab:ContactDataByTypeTemplateSelector x:Key="contactDataByTypeTemplateSelector"/>
</Window.Resources>
<Grid x:Name="grid">
<ListView ItemsSource="{Binding ContactDataCollection, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TestCustomTab:Window1}}}">
<ListView.View>
<GridView>
<GridViewColumn Header="Column" Width="200" CellTemplateSelector="{StaticResource contactDataByTypeTemplateSelector}">
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
Lets assume that your ContactData looks as follows:
public class ContactData : INotifyPropertyChanged
{
private string _name;
private int _homePhone;
private int _mobilePhone;
private string _value;
private ContactDataType _dataType;
public ContactData()
{
}
public ContactData(string name, int homePhone, int mobilePhone, string value, ContactDataType dataType)
{
_name = name;
_dataType = dataType;
_value = value;
_mobilePhone = mobilePhone;
_homePhone = homePhone;
}
#region Implementation of INotifyPropertyChanged
public ContactDataType DataType
{
get { return _dataType; }
set
{
if (_dataType == value) return;
_dataType = value;
raiseOnPropertyChanged("DataType");
}
}
public string Name
{
get { return _name; }
set
{
if (_name == value) return;
_name = value;
raiseOnPropertyChanged("Name");
}
}
public int HomePhone
{
get { return _homePhone; }
set
{
if (_homePhone == value) return;
_homePhone = value;
raiseOnPropertyChanged("HomePhone");
}
}
public int MobilePhone
{
get { return _mobilePhone; }
set
{
if (_mobilePhone == value) return;
_mobilePhone = value;
raiseOnPropertyChanged("MobilePhone");
}
}
public string Value
{
get { return _value; }
set
{
if (_value == value) return;
_value = value;
raiseOnPropertyChanged("Value");
raiseOnPropertyChanged("Symbols");
}
}
public ReadOnlyCollection<char> Symbols
{
get
{
return !string.IsNullOrEmpty(_value) ? new ReadOnlyCollection<char>(_value.ToCharArray()) : new ReadOnlyCollection<char>(new List<char>());
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void raiseOnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
It has DataType property of type ContactDataType which is enum:
public enum ContactDataType
{
Working,
Personal
}
In ability to have different DataTemplates for the same entities differentiated by some feature you need to use DataTemplateSelector. The technique is in inheriting from DataTemplateSelector and overriding SelectTemplate method. In our case:
public class ContactDataByTypeTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var contactData = item as ContactData;
var control = container as FrameworkElement;
if (contactData != null & control != null)
switch (contactData.DataType)
{
case ContactDataType.Working:
return control.TryFindResource("workingTemplate") as DataTemplate;
case ContactDataType.Personal:
return control.TryFindResource("personalTemplate") as DataTemplate;
default:
return base.SelectTemplate(item, container);
}
return base.SelectTemplate(item, container);
}
}
Here is Window1 class in code behind:
public partial class Window1 : Window
{
private ObservableCollection<ContactData> _contactDataCollection = new ObservableCollection<ContactData>();
public Window1()
{
ContactDataCollection.Add(new ContactData("test1", 0, 1, "value1", ContactDataType.Working));
ContactDataCollection.Add(new ContactData("test2", 0, 1, "value2", ContactDataType.Working));
ContactDataCollection.Add(new ContactData("test3", 0, 1, "value3", ContactDataType.Working));
ContactDataCollection.Add(new ContactData("test4", 0, 1, "value4", ContactDataType.Personal));
ContactDataCollection.Add(new ContactData("test5", 0, 1, "value5", ContactDataType.Personal));
InitializeComponent();
}
public ObservableCollection<ContactData> ContactDataCollection
{
get { return _contactDataCollection; }
}
}
Now explanation:
We created some class that we need to represent to user (ContactData) and let him to have feature - ContactDataType.
We created 2 DataTemplates in resources (x:Key is important) for ContactDataType.Working and ContactDataType.Personal
We created DataTemplateSelector to have ability switch templates by feature.
In our first GridViewColumn we defined CellTemplateSelector and bind to it our ContactDataByTypeTemplateSelector.
In runtime whenever the collection changes ContactDataByTypeTemplateSelector select to us template based on item feature and we may have any number of templates for any number of defined features.
Notice: change TestCustomTab for your namespace.
For the last question, you can use a button and template it to include an image.

Resources