How to access UI elements in multiple threads? - wpf

I had a standard foreach loop which I later turned into a Parallel.Foreach(). However in my loop I have areas where I access the UI elements and get and set the UI elements info.
So when I run it I get an error that I cannot access the element as another thread has access to it. There are multiple elements I need to access and the x:Name are stored in the list.
How do I get past this?
Parallel.ForEach(calculatedTestVariables, variable =>
{
string idName = "id_" + variable.id;
var textBox = this.FindName(idName) as TextBox; //need the text from this TextBox
//some calculations
int x = 1 + 2 + 3 + 4
textBox.Text = x.toString();
});

To do this, you need data binding. You may follow such a way,
First create your view model and update your properties:
public class MainViewModel : BindableBase
{
private string m_TextProgress;
public string TextProgress
{
get { return m_TextProgress; }
set { SetProperty(ref m_TextProgress, value); }
}
public void Run()
{
List<Variable> calculatedTestVariables = new List<Variable>();
for (int i = 0; i < 5000; i++)
{
Variable item = new Variable();
item.id = i;
calculatedTestVariables.Add(item);
}
Parallel.ForEach(calculatedTestVariables, variable =>
{
string idName = "id_" + variable.id;
//some calculations
int x = 1 + 2 + 3 + 4;
TextProgress = "" + variable.id + x;
});
}
}
Set your DataContext
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<Grid>
<StackPanel>
<TextBlock
Width="120"
Height="30"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Text="{Binding TextProgress, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" />
<Button
Width="120"
Height="30"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Click="Button_Click"
Content="Calculate" />
</StackPanel>
</Grid>
and call your Run method from GUI side

Related

item_Expand analogue in MVVM

I am writing an application like folder explorer using MWF(MVVM pattern).
Previously I wrote it without using an MVVM, and I am curious how should I do it in MVVM approach. The problem is, that I want my application to load the "children" nodes, only when the "parent" node is clicked (expand button). Now I have this piece of code Model:
public class Node : ViewModelBase
{
public string Name
{
get => name;
set
{
name = value;
OnPropertyChanged("Name");
}
}
public long Size
{
get => size;
set
{
size = value;
OnPropertyChanged("Size");
}
}
private List<Node> nodes;
private string name;
private long size;
public List<Node> Nodes
{
get
{
return nodes;
}
set
{
nodes = value;
OnPropertyChanged("Nodes");
}
}
}
ViewModel:
public class MainWindowViewModel : ViewModelBase
{
private List<Node> nodes;
public MainWindowViewModel()
{
var logicDrives = DriveInfo.GetDrives();
Nodes = new();
foreach (var drive in logicDrives)
{
var node = new Node
{
Name = drive.Name,
Size = (long)default,
Nodes = new()
{
new Node{Name = "dummy", Size = 12, Nodes = new()}
}
};
Nodes.Add(node);
}
}
public List<Node> Nodes
{
get => nodes;
set
{
nodes = value;
OnPropertyChanged("Drives");
}
}
}
XAML:
<Window.DataContext>
<VM:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<TreeView x:Name="MainTreeView"
HorizontalAlignment="Stretch"
Margin="10"
VerticalAlignment="Stretch"
ItemsSource="{Binding Nodes}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type VM:Node}"
ItemsSource="{Binding Path=Nodes, UpdateSourceTrigger=PropertyChanged}">
<StackPanel Orientation="Horizontal">
<TextBlock Width="200"
Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"
Margin="0 0 10 0" />
<TextBlock Width="100"
Text="{Binding Size, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
In my ViewModel, I've created the drives, and populate them with "dummys" in order to make them expandable, now it looks like this
And I want to load the "child nodes" instead of dummys, when clicking on the expand button.
P.S: I already have all functions for GetDirectories, and CalculateDirectories size, I just don't understand how to call them, when some node is clicked. THX

How can I WPF bind dynamically created checkboxes to dynamically created series in LiveCharts?

I have a liveChart and am creating checkboxes for each item in a list. This list also has data for each series in liveCharts. How do I bind my dynamically created checkboxes with each individual LiveCharts.LineSeries from my data?
I've created the checkboxes:
<!-- Creating checkboxes by binding to list -->
<ListView ItemsSource="{Binding ElementItemList}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled" Width="600">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=ElementName}" />
<CheckBox IsChecked="{Binding Path=ElementIsSelected}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListView>
<!-- Display the chart -->
<Grid Grid.Row="1" x:Name="TestGrid"></Grid>
So I assume that you want to have a CheckBox representing each LineSeries in your SeriesCollection.
So I would have two public properties, one for the SeriesCollection and the other for the list of CheckBox controls.
public SeriesCollection SeriesCollection { get; set; }
public List<CheckBox> CheckBoxCollection { get; set; }
Then following is a function that mimics dynamically creating your LineSeries and CheckBox controls since you didn't provide that code. It is important to have some sort of a connection between the CheckBox controls and your line series, and in this case I decided to set LineSeries.Title and CheckBox.Name the same.
Also note that in order to have the CheckBox do something upon checking/unchecking, you'd need to register two events for each.
public void DynamicallyCreateStuff()
{
SeriesCollection = new SeriesCollection();
CheckBoxCollection = new List<CheckBox>();
var count = 3;
var val1 = new List<double>() { 1, 2, 3 };
var val2 = new List<double>() { 9, 5, 3 };
var val3 = new List<double>() { 1, 4, 9 };
for (int i = 1; i <= count; i++)
{
var name = string.Format("LineSeries{0}", i);
var checkBox = new CheckBox
{
Name = name,
Content = name,
Margin = new Thickness() { Left = 8, Top = 8, Right = 8, Bottom = 8 },
IsChecked = true
};
checkBox.Checked += DynamicCheckBoxChecked;
checkBox.Unchecked += DynamicCheckBoxUnchecked;
CheckBoxCollection.Add(checkBox);
var lineSeries = new LineSeries
{
Title = name
};
if (i == 1)
{
lineSeries.Values = new ChartValues<double>(val1);
}
else if (i == 2)
{
lineSeries.Values = new ChartValues<double>(val2);
}
else if (i == 3)
{
lineSeries.Values = new ChartValues<double>(val3);
}
SeriesCollection.Add(lineSeries);
}
}
In my case, I decided to have the corresponding series become visible/hidden upon clicking the CheckBox, so my check/uncheck methods look like this:
private void DynamicCheckBoxChecked(object sender, EventArgs e)
{
ShowHideSeries(sender, Visibility.Visible);
}
private void DynamicCheckBoxUnchecked(object sender, EventArgs e)
{
ShowHideSeries(sender, Visibility.Collapsed);
}
private void ShowHideSeries(object sender, Visibility visibility)
{
var checkBox = (CheckBox)sender;
var found = SeriesCollection.FirstOrDefault(x => x.Title == checkBox.Name);
if (found != null)
{
var series = (LineSeries)found;
series.Visibility = visibility;
}
}
I didn't use a ViewModel in order to save time and for the sake of simplicity, so my MainWindow constructor looks like this:
public MainWindow()
{
InitializeComponent();
DynamicallyCreateStuff();
DataContext = this;
}
And XAML is pretty bare bones here:
<Window x:Class="SOLineCharts.MainWindow"
....
xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
mc:Ignorable="d"
WindowStartupLocation="CenterScreen"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ItemsControl Grid.Column="0"
ItemsSource="{Binding CheckBoxCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" ScrollViewer.HorizontalScrollBarVisibility="Disabled"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<lvc:CartesianChart Series="{Binding SeriesCollection}" Grid.Column="1"/>
</Grid>
</Window>
Result:
Upon loading:
Unchecking one check box:

How to bind two elements to single source in WPF

My scenario is that there are two controls. One in which you set up minutes and second in which you specify seconds.
Both of them should be bound to single property in view model. This property is of type string. This string is in format [hh:mm:ss]. So changing value in "minutes" control should change 'mm' portion of the string and changing the value in "seconds" control should change the 'ss' portion of the string.
Thanks in advance
Here is a 3-property ViewModel working solution if you are using TimeSpan and its range is between 0 and 59h 59s. I have not fully tested and conditions/validation will change based on requirements. I used TimeSpan.TotalSeconds because that's the resolution we needed; meaning, when setting the TimeSpan to a new value, we would just set the total number of seconds through the public property. An alternative could be to have 2 TimeSpan properties in your ViewModel, then when setting the public property, you could call _item.TotalSeconds = VMMinutes.TotalSeconds + VMSeconds.TotalSeconds.TotalSeconds. Basically you have many design options here.
MainWindow.xaml:
<Grid>
<StackPanel>
<Border Height="60" BorderBrush="Black" BorderThickness="1">
<StackPanel Orientation="Horizontal">
<Label Content="Minutes"/>
<TextBox Text="{Binding Minutes}" />
<Label Content="Seconds"/>
<TextBox Text="{Binding Seconds}" />
</StackPanel>
</Border>
<Border Height="60" BorderBrush="Black" BorderThickness="1">
<StackPanel Orientation="Horizontal">
<Label Content="Total Seconds"/>
<TextBox Text="{Binding TotalSeconds}" />
</StackPanel>
</Border>
</StackPanel>
</Grid>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ItemViewModel(new Item(new TimeSpan(0, 3, 59)));
}
}
ItemViewModel.cs:
public class ItemViewModel : INotifyPropertyChanged
{
private readonly Item _item;
public event PropertyChangedEventHandler PropertyChanged;
public ItemViewModel(Item item)
{
_item = item;
}
public string TotalSeconds
{
get
{
return _item.TotalSeconds.ToString();
}
set
{
double newTotSecs;
if(!string.IsNullOrEmpty(value))
{
if(double.TryParse(value, out newTotSecs))
{
_item.TotalSeconds = newTotSecs;
NotifyPropertyChanged();
NotifyPropertyChanged("Minutes");
NotifyPropertyChanged("Seconds");
}
}
}
}
public string Seconds
{
get
{
return (_item.TotalSeconds % 60).ToString();
}
set
{
int newVal;
if(!string.IsNullOrEmpty(value))
{
if(int.TryParse(value, out newVal))
{
if(newVal >= 0 && newVal <= 59)
{
int totMinSec;
if(int.TryParse(Minutes, out totMinSec))
{
_item.TotalSeconds = (totMinSec * 60) + newVal;
NotifyPropertyChanged();
NotifyPropertyChanged("TotalSeconds");
}
}
}
}
}
}
public string Minutes
{
get
{
return ((int)(_item.TotalSeconds / 60)).ToString();
}
set
{
int newVal;
if(!string.IsNullOrEmpty(value))
{
if(int.TryParse(value, out newVal))
{
if(newVal >= 0 && newVal <= 59)
{
int totSec;
if(int.TryParse(Seconds, out totSec))
{
_item.TotalSeconds = totSec + (newVal * 60);
NotifyPropertyChanged();
NotifyPropertyChanged("TotalSeconds");
}
}
}
}
}
}
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Item.cs:
public class Item
{
private TimeSpan _time;
public double TotalSeconds
{
get
{
return _time.TotalSeconds;
}
set
{
if(value >= 0)
{
_time = new TimeSpan(0, 0, (int)value);
}
}
}
public Item(TimeSpan time)
{
_time = time;
}
}
Note: Your other option is to use a Converter, which I haven't provided a solution for. I think it could end up being cleaner in the long run since all you really need to pass to back and forth is the converter is total number of seconds.
I would use NETScape's approach above, but encapsulate it in a user control. The user control XAML would be something like:
<UserControl>
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Minutes" Grid.Row="0" Grid.Column="0"/>
<TextBox Text="{Binding InternalMinutes}" Grid.Row="0" Grid.Column="1"/>
<TextBlock Text="Seconds" Grid.Row="1" Grid.Column="0"/>
<TextBox Text="{Binding InternalSeconds}" Grid.Row="1" Grid.Column="1"/>
</Grid>
</UserControl>
Then in the code-behind, you would have a Dependency Property for the actual DateTime object, and properties to bind against (you could use a view model for this, or just go off of TextChanged. When its all View logic, its ok!).
An example property would be:
public int InternalSeconds
{
get { return ExternalTime.Seconds; }
set
{
ExternalTime.Seconds = value;
NotifyPropertyChanged();
}
}
Again, there are multiple approaches here, you could use a converter in order to use an intermediate object. ExternalTime is the DP here, make sure to handle its Changed event if you expect the value to change outside of this control.

binding slider value in MVVM

I have a problem in slider value data binding in MVVM. When value gets changed my expected value isn’t achieved. How can I solve my problem?
I have a listbox, a slider and a textblock. listbox is bound to ListImage, slider value and textblock text is bound to CurrentImage. One button with command navigate the lisbox item. CurrentImage is a property in the viewmodel. When I change slider’s setter, new value of setter put to current value of slider’s setter and the arrangement of listbox gets corrupted. For example when value of my slider’s setter set to 50 and I change value of slider to 10 again. My slider value navigate from 10 to 50 and not more. It must be navigate whole of the listbox but it can’t. There is my code:
XAML:
<TextBlock Text="{Binding CurrentImage.Index}"/>
<Slider Height="23" HorizontalAlignment="Left" Margin="12,305,0,0" Name="slider1" VerticalAlignment="Top" Width="479" Maximum="{Binding ListImage.Count, Mode=OneTime}"
Value="{Binding CurrentImage.Index, Mode=TwoWay}"
SmallChange="1" />
<Button Content="{Binding DisplayPlay}" Command="{Binding PlayCommand}" Height="23" HorizontalAlignment="Left" Margin="507,305,0,0" Name="button1" VerticalAlignment="Top" Width="75" />
<ListBox Height="129" HorizontalAlignment="Left" Margin="12,334,0,0" ItemsSource="{Binding ListImage}" SelectedItem="{Binding CurrentImage,Mode=TwoWay}"
VerticalAlignment="Top" Width="472">
viewmodel:
public class MainViewModel : ViewModelBase
{
public ICommand PlayCommand { get; set; }
private DispatcherTimer _Timer;
public ImageDTO Image { get; set;}
DataAccess AC = new DataAccess();
public byte[] Bytes { get; set; }
public MainViewModel()
{
ListImage = AC.OpenImages();
CurrentImage = ListImage[0];
Bytes = CurrentImage.Bytes;
this.PlayCommand = new DelegateCommand(Play, CanPlay);
DisplayPlay = "Play";
_Timer = new DispatcherTimer();
_Timer.Interval = new TimeSpan(0, 0, 0, 0, 2000 / 30);
_Timer.Tick += new EventHandler(timer_Tick);
}
private string _DisplayPlay;
public string DisplayPlay
{
get { return _DisplayPlay; }
set
{
if (_DisplayPlay != value)
{
_DisplayPlay = value;
OnPropertyChanged("DisplayPlay");
}
}
}
private List<ImageDTO> _ListImage;
public List<ImageDTO> ListImage
{
get { return _ListImage; }
set
{
if (_ListImage != value)
_ListImage = value;
OnPropertyChanged("ListImage");
}
}
private ImageDTO _CurrentImage;
public ImageDTO CurrentImage
{
get { return _CurrentImage; }
set
{
if (_CurrentImage != value)
{
_CurrentImage = value;
OnPropertyChanged("CurrentImage");
}
}
}
public bool CanPlay(object parameter)
{
return true;
}
public void Play(object parameter)
{
if (DisplayPlay == "Play")
{
DisplayPlay = "Pause";
_Timer.Start();
}
else
{
_Timer.Stop();
DisplayPlay = "Play";
}
}
private void timer_Tick(object sender, EventArgs e)
{
int position = ListImage.FindIndex(x => x.Index == CurrentImage.Index);
position++;
if (position == ListImage.Count)
{
position = 0;
}
else
{
CurrentImage = ListImage[position];
}
}
Maybe this is what you want:
<StackPanel>
<ListBox x:Name="ImageListBox" IsSynchronizedWithCurrentItem="True">
<ListBoxItem>Image1</ListBoxItem>
<ListBoxItem>Image2</ListBoxItem>
<ListBoxItem>Image3</ListBoxItem>
<ListBoxItem>Image4</ListBoxItem>
</ListBox>
<Slider Value="{Binding ElementName=ImageListBox, Path=SelectedIndex}"
Maximum="{Binding ElementName=ImageListBox, Path=Items.Count}"/>
</StackPanel>
You probably want to handle the max value nicer than in this sample
In your code the value of slider is bound to CurrentImage index, so when you change the value of slider then the index of the current image will be changed. Assumed that the current image index is 5 then your slider’s value will be 5 then if you move the slider pointer to a position like 10 then the Current image index will be set to 10 it means that your current image will be modified, so it means that it would not navigate to the 10th element of list, but it modifies the index of the image (with index of 5) and set its index to 10, in other words, you would not have a image with index of 5 anymore and 2 of your images are going to have the same index (10).
As a solution, you can add another property like “Index” into your viewmodel, and bind the slider value to that one, so at get method return the index of the selected item in list, and at the set method change the selected item in the list and call OnPropertyChanged and pass “Index” as the parameter.

Problem with DataBinding a ComboBox in Silverlight with MVVM Light

After struggling for about one week with a problem in Silverlight 4 + MVVM-Light toolkit and after searching the web without success I want to present my problem here and hope that somebody could give me some hints.
I want to present the simplified programm:
My model classes:
Person
public class Person
{
private decimal _cod_Person;
public decimal Cod_Person
{
get { return _cod_Person; }
set { _cod_Person = value; }
}
private string _des_Person;
public string Des_Person
{
get { return _des_Person; }
set { _des_Person = value; }
}
}
PersonInfo
public class PersonInfo
{
private decimal _cod_person;
public decimal Cod_person
{
get { return _cod_person; }
set { _cod_person = value; }
}
private string _des_note;
public string Des_note
{
get { return _des_note; }
set { _des_note = value; }
}
}
Here my ViewModel:
public class PersonViewModel : ViewModelBase
{
public RelayCommand<Model.PersonInfo> save_Click { get; private set; }
public PersonViewModel()
{
save_Click = new RelayCommand<Model.PersonInfo>(personInfo =>
{
SavePerson(personInfo);
});
//the content of the combo box is defined
AllPerson = new ObservableCollection<Model.Person>
{
new Model.Person(){
Cod_Person = 1,
Des_Person = "Name 1"
},
new Model.Person(){
Cod_Person = 2,
Des_Person = "Name 2"
}
};
//an empty PersonInfo is created, which the UI will work on
ChoosenPerson = new Model.PersonInfo();
}
private void SavePerson(Model.PersonInfo personInfo)
{
//here some safing processing could be done...
//but is omitted here
//and here a new PersonInfo is assigned the ChoosenPerson
ChoosenPerson = new Model.PersonInfo();
}
public const string AllPersonPropertyName = "AllPerson";
private ObservableCollection<Model.Person> _allPersons = null;
public ObservableCollection<Model.Person> AllPerson
{
get
{
return _allPersons;
}
set
{
if (_allPersons == value)
{
return;
}
var oldValue = _allPersons;
_allPersons = value;
RaisePropertyChanged(AllPersonPropertyName, oldValue, value, true);
}
}
public const string ChoosenPersonPropertyName = "ChoosenPerson";
private Model.PersonInfo _choosenPerson = null;
public Model.PersonInfo ChoosenPerson
{
get
{
return _choosenPerson;
}
set
{
if (_choosenPerson == value)
{
return;
}
var oldValue = _choosenPerson;
_choosenPerson = value;
// Update bindings and broadcast change using GalaSoft.MvvmLight.Messenging
RaisePropertyChanged(ChoosenPersonPropertyName, oldValue, value, true);
}
}
}
In my view (PersonView) I have a combo box, a text box and a button:
<UserControl x:Class="TEST_STACKOVERFLOW.PersonView"
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"
mc:Ignorable="d"
DataContext="{Binding Source={StaticResource Locator}, Path=PersonViewModel.ChoosenPerson}" d:DesignHeight="258" d:DesignWidth="341">
<Grid>
<ComboBox Height="23" HorizontalAlignment="Left" Margin="84,51,0,0" Name="comboBox1" VerticalAlignment="Top" Width="120" ItemsSource="{Binding Source={StaticResource Locator}, Path=PersonViewModel.AllPerson}" DisplayMemberPath="Des_Person" SelectedValuePath="Cod_Person" SelectedValue="{Binding Path=Cod_person, Mode=TwoWay}" />
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="159,199,0,0" Name="button1" VerticalAlignment="Top" Width="75" Command="{Binding Source={StaticResource Locator}, Path=PersonViewModel.save_Click}" CommandParameter="{Binding}" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="94,107,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" Text="{Binding Path=Des_note, Mode=TwoWay}" />
</Grid>
When the button is clicked the first time everything works. The parameter (PersonInfo) passed after the button was clicked contains the selected ComboBox value (Cod_person) and the inserted text(Des_note). In the method SavePerson a new PersonInfo instance is assigned to the ChoosenPerson object. The problem occurs when I click the button a second time. Then as parameter after the button was clicked I get an instance of the PersonInfo class which contains the correct inserted text in the text box but as value selected in the combo box I always get 0, independet of what I choosed in the combo box. This problems occurs just in the case, that I use as combo box items instances of a class. If I use as combo box items just string values this problem does not occur. But I have to use instances of a class in my combo box.
I hope that somebody has a hint.
Thanks!
PS:
Interesting is still the fact, that when changing the assignment of the ChoosenPerson from "ChoosenPerson = new Model.PersonInfo();" to _choosenPerson = new Model.PersonInfo(); this means by assignment through use of private members instead of access methods, the second time the button is clicked, the values are written into the parameter of the button correctly. The only thing is that the values that were inserted the last time are not deleted. They are shown after the first button click. But they are not shown when the assignment of a new empty ChoosenPerson is made throuh access methods...
I can't explain this behavior. Who can help me?? Thank you.
Adam was on the right track. I would assign your PersonViewModel to the DataContext instead of assigning it to the ChoosenPerson.
<UserControl
x:Class="TEST_STACKOVERFLOW.PersonView"
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"
DataContext="{Binding Source={StaticResource Locator}, Path=PersonViewModel}"
mc:Ignorable="d" d:DesignHeight="258" d:DesignWidth="341">
<Grid>
<ComboBox Height="23" HorizontalAlignment="Left" Margin="84,51,0,0" Name="comboBox1" VerticalAlignment="Top" Width="120" ItemsSource="{Binding AllPerson}" DisplayMemberPath="Des_Person" SelectedValuePath="Cod_Person" SelectedItem="{Binding Path=ChoosenPerson, Mode=TwoWay}" />
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="159,199,0,0" Name="button1" VerticalAlignment="Top" Width="75" Command="{Binding save_Click}" CommandParameter="{Binding SelectedItem, ElementName=comboBox1}" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="94,107,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" Text="{Binding ChoosenPerson.Des_Person, Mode=TwoWay}" />
</Grid>
</UserControl>
I would also totally get rid of the PersonInfo class. You do not need this class. You can work with the Person object directly. Change your references from PersonInfo to the Person class. Also, if you want to clear out the current selection in the ComboBox, set the ChoosenPerson property to null. Do not new up an instance of a class. The ChoosenPerson property needs to either be null or one of the objects in the AllPerson collection.
public class PersonViewModel : ViewModelBase
{
public RelayCommand<Person> save_Click { get; private set; }
public PersonViewModel()
{
save_Click = new RelayCommand<Person>(personInfo =>
{
SavePerson(personInfo);
});
//the content of the combo box is defined
AllPerson = new ObservableCollection<Person>
{
new Person(){
Cod_Person = 1,
Des_Person = "Name 1"
},
new Person(){
Cod_Person = 2,
Des_Person = "Name 2"
}
};
//an empty PersonInfo is created, which the UI will work on
ChoosenPerson = new Person();
}
private void SavePerson(Person personInfo)
{
//here some safing processing could be done...
//but is omitted here
//and here a new PersonInfo is assigned the ChoosenPerson
ChoosenPerson = null;
}
public const string AllPersonPropertyName = "AllPerson";
private ObservableCollection<Person> _allPersons = null;
public ObservableCollection<Person> AllPerson
{
get
{
return _allPersons;
}
set
{
if (_allPersons == value)
{
return;
}
var oldValue = _allPersons;
_allPersons = value;
RaisePropertyChanged(AllPersonPropertyName, oldValue, value, true);
}
}
public const string ChoosenPersonPropertyName = "ChoosenPerson";
private Person _choosenPerson = null;
public Person ChoosenPerson
{
get
{
return _choosenPerson;
}
set
{
if (_choosenPerson == value)
{
return;
}
var oldValue = _choosenPerson;
_choosenPerson = value;
// Update bindings and broadcast change using GalaSoft.MvvmLight.Messenging
RaisePropertyChanged(ChoosenPersonPropertyName, oldValue, value, true);
}
}
}
I think you want something like this:
<UserControl x:Class="TEST_STACKOVERFLOW.PersonView"
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"
mc:Ignorable="d"
DataContext="{Binding Source={StaticResource Locator}, Path=PersonViewModel}" d:DesignHeight="258" d:DesignWidth="341">
<Grid>
<ComboBox Height="23" HorizontalAlignment="Left" Margin="84,51,0,0" Name="comboBox1" VerticalAlignment="Top" Width="120" ItemsSource="{Binding AllPerson}" DisplayMemberPath="Des_Person" SelectedValuePath="Cod_Person" SelectedValue="{Binding Path=ChoosenPerson, Mode=TwoWay}" />
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="159,199,0,0" Name="button1" VerticalAlignment="Top" Width="75" Command="{Binding save_Click}" CommandParameter="{Binding ChoosenPerson}" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="94,107,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" Text="{Binding Path=Des_note, Mode=TwoWay}" />
</Grid>
I updated most of the bindings. Once you set the UserControl's data context, the rest of the controls inside can simply reference the property name, instead of having to use the Locator. I also think you had a few things pointing to the wrong places.

Resources