I'm using a ComboBox (WPF 4.0) to show user defined paragraph styles for an editor app. The ComboBox has two columns:
(1) Unformatted name of the paragraph style
(2) Text "abcABC123", in some properties formatted according to the paragraph style in the first column
These are public properties of the user defined paragraph style class (where the last 3 are no ResourceKeys but variables containing ResourceKeys):
_NameInternal
_NameUI
_ResourceKey_background
_ResourceKey_foreground
_ResourceKey_fontFamily
Problem: ComboBox shows a SelectedItem. If I open a dialog, change one or more of the three Binding properties of the SelectedItem
(Background, Foreground, FontFamily) and close dialog then the SelectedItem of the ComboBox is not updated. But if I drop it down it shows
the new formatting.
Is there a way to solve this in Xaml instead of C#?
<Window.Resources>
<local2:_2StylesPara x:Key="_2stylesPara" />
<CollectionViewSource x:Key="_collectionViewSource_stylesPara" Source="{StaticResource _2stylesPara}">
<CollectionViewSource.SortDescriptions>
<!-- Requires 'xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"' declaration. -->
<scm:SortDescription PropertyName="_NameUI" Direction="Ascending"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</Window.Resources>
<ComboBox Name="_cbStylesPara" HorizontalAlignment="Left"
ItemsSource="{Binding Source={StaticResource _collectionViewSource_stylesPara}}"
SelectedValuePath="_NameInternal" IsSynchronizedWithCurrentItem="True" >
<ComboBox.Resources>
<local2:_2ResourceLookupConverter x:Key="_resourceLookupConverter"/>
</ComboBox.Resources>
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{Binding _NameUI}" Grid.Column="0" VerticalAlignment="Center" />
<TextBlock Grid.Column="1" Text="abcABC123" Margin="3,0,0,0"
Background="{Binding _ResourceKey_background, Converter={StaticResource _resourceLookupConverter}}"
Foreground="{Binding _ResourceKey_foreground, Converter={StaticResource _resourceLookupConverter}}"
FontFamily="{Binding _ResourceKey_fontFamily, Converter={StaticResource _resourceLookupConverter}}"/>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Code behind:
public class _2ResourceLookupConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return App.Current.TryFindResource(value);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return Binding.DoNothing;
}
}
Here are the 2 classes for the user defined paragraph styles:
public class _2StylesPara : ObservableCollection<_2StylePara>
// ObservableCollection implements INotifyPropertyChanged
{
public _2StylesPara(){}
}
public class _2StylePara
{
public event PropertyChangedEventHandler PropertyChanged;
// This method is not reached if Background, Foreground or FontFamily changes
private void SetValue<T>(ref T property, T value, string propertyName = null)
{
if (object.Equals(property, value) == false)
{
property = value;
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
private string _nameInternal = string.Empty;
private string _nameUI = string.Empty;
private string _resourceKey_background = string.Empty;
private string _resourceKey_foreground = string.Empty;
private string _resourceKey_fontFamily = string.Empty;
private string _resourceKey_nameUI = string.Empty;
private string _resourceKey_style = string.Empty;
private Style _style = null;
private ResourceDictionary _valuesRD = null;
private string _pathToValuesRD = string.Empty;
public string NameInternal
{
get { return this._nameInternal; }
set { SetValue(ref this._nameInternal, value); }
}
public string NameUI
{
get { return this._nameUI; }
set { SetValue(ref this._nameUI, value); }
}
public string ResourceKey_background
{
get { return this._resourceKey_background; }
set { SetValue(ref this._resourceKey_background, value); }
}
public string ResourceKey_foreground
{
get { return this._resourceKey_foreground; }
set { SetValue(ref this._resourceKey_foreground, value); }
}
public string ResourceKey_fontFamily
{
get { return this._resourceKey_fontFamily; }
set { SetValue(ref this._resourceKey_fontFamily, value); }
}
public string ResourceKey_nameUI
{
get { return this._resourceKey_nameUI; }
set { SetValue(ref this._resourceKey_nameUI, value); }
}
public string ResourceKey_style
{
get { return this._resourceKey_style; }
set { SetValue(ref this._resourceKey_style, value); }
}
public Style Style
{
get { return this._style; }
set { SetValue(ref this._style, value); }
}
public ResourceDictionary ValuesRD
{
get { return this._valuesRD; }
set { SetValue(ref this._valuesRD, value); }
}
public string PathToValuesRD
{
get { return this._pathToValuesRD; }
set { SetValue(ref this._pathToValuesRD, value); }
}
// Constructor
public _2StylePara(Style sty, string styleNameInternal, string styleNameUI, string resourceKey_style, string resourceKey_nameUI,
string resourceKey_foreground, string resourceKey_background, string resourceKey_fontFamily,
ResourceDictionary valuesRD, string pathToValuesRD)
{
_style = sty;
_nameInternal = styleNameInternal; // [ "_sty001" ]
_nameUI = styleNameUI; // [ "Standard" ]
_resourceKey_style = resourceKey_style; // [ "_stylePara001" ]
_resourceKey_nameUI = resourceKey_nameUI; // [ "_nameUi001 ]
_resourceKey_foreground = resourceKey_foreground; // [ "_brush_textcolor001" ]
_resourceKey_background = resourceKey_background; // [ "_brush_backcolor001" ]
_resourceKey_fontFamily = resourceKey_fontFamily; // [ "_fontFamily001" ]
_valuesRD = valuesRD; // This ResourceDictionary contains all style values
_pathToValuesRD = pathToValuesRD; // [ "...\Resources\1ParaStyleValuesRD001.xaml" ]
}
}
If I understood it properly, _ResourceKey_background and other properties are properties of your user defined paragraph style class that is contained in your _2sytlesPara collection. The behavior what you experience is when you change these properties in the background, the view is not updated.
In this case if you update you model (aka one of the UDF style class instances) the binding should be notified about the update. It is done by the INotifyPropertyChanged.PropertyChanged event that should be triggered by the model.
The binding automatically works the other way around, therefore your model is updated when you change something on the view.
The standard property pattern for notification is:
class Model : INotifyPropertyChanged
{
private int _Name = default(int);
public int Name
{
get { return _Name; }
set
{
SetValue(ref this._Name, value);
}
}
private void SetValue<T>(ref T property, T value, [CallerMemberName]string propertyName = null)
{
if (object.Equals(property, value) == false)
{
property = value;
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
I hope I understood your problem and I could help.
UPDATE:
IObservableCollection informs the binding mechanizm only about additions and deletions in the collection. Changes of nested property values of the items still not going to be reflected automatically.
Regarding your code I would suggest to try the following modifications:
First of all, I would not recommend start public propertry names with underscore. According to MS naming conventions names started with underscore are usually private backing fields.
So, alter the corresponding properties like:
private string _ResourceKey_nameUI = string.Empty;
public string ResourceKey_nameUI
{
get { return this._ResourceKey_nameUI; }
set { SetValue(ref this._ResourceKey_nameUI, value); }
}
Also alter the bindings to the properties:
<TextBlock Grid.Column="1" Text="abcABC123" Margin="3,0,0,0"
Background="{Binding ResourceKey_background, Converter={StaticResource _resourceLookupConverter}}"
Foreground="{Binding ResourceKey_foreground, Converter={StaticResource _resourceLookupConverter}}"
FontFamily="{Binding ResourceKey_fontFamily, Converter={StaticResource _resourceLookupConverter}}"/>
UPDATE 2
WPF binding checks if the bound model instances implement INotifyPropertyChanged interface.
Please modify your class declaration as:
public class _2StylePara : INotifyPropertyChanged
{
//...
}
Furthermore, when you change your values in the background, you should use the properties not the backing fields. So, when you change NameUI the SetValue method is going to be called, and it will notify the binding to refresh the TextBlock. The binding should also point to the property, not the backing field.
Therefore: Text="{Binding NameUI}" NOT Text="{Binding _NameUI}"
Plese, mark as answer if it helped. Thanks.
Related
I'm no sure what the right way to bind the combobox control is to my view model.
I'm using a MVVM approach so in my viewmodel i'm loading all the CDType data and am binding their source in combination with the actual record properties model.CDType.id and model.CDType.name.
What happens is that when i change the text? - i keep my old id (from the load routine) and get a new text value from the combobox binding therefor always writing over the existing record instead of creating a new one.
How can i make my combobox set the id to 0 / undefined if the text isn't in the list? (manually? - kind of lame)
Anything will help - thanks!
TL;DR: Editable Combo-box not updating ID on text change.
Sample Xaml binding:
<ComboBox x:Name="ddlCDType"
IsTextSearchEnabled="True"
IsTextSearchCaseSensitive="False"
StaysOpenOnEdit="True"
TextSearch.TextPath="Name"
ItemsSource="{Binding CDTypes}"
SelectedValue="{Binding Assignment.CDType.ID}"
Text="{Binding Assignment.CDType.Name,
UpdateSourceTrigger=PropertyChanged,
NotifyOnValidationError=True,
ValidatesOnDataErrors=True}"
DisplayMemberPath="Name"
SelectedValuePath="ID"
IsEditable="True"
HorizontalAlignment="Left"
Margin="98,10,0,0"
VerticalAlignment="Top"
Width="136" />
as I can understand you need update the previous last selected value when the name of selected ComboBox item is changed manually and this name is not presented in the ComboBox ItemSource. Here is my suggestion, it is combination of your ComboBox control data binding and logic defined in the ViewModel.
Explanation of binding
Since the ComboBox ItemsSource is the collection of Cd (the collection of ComboModels), thus the TextSearch.TextPath binding should be defined as CDType.Name where the CdType is property defined in the ComboModel that describes the sub-model, and the Name is an actual search path which is describing the sub-model.
The ComboBox Text property is binded to AssignmentText property to trigger the updating logic when the combo is lost the focus (as defined in binding).
The ItemsSource is trivial as defined in your sample code.
Selected value will bring a whole model to update (in case we change the selected value name).
Since the ComboBox ItemsSource is the collection of Cd (let's call this ComboModel) the DisplayMemberPath binding should be defined as CDType.Name where the CdType is property defined in the ComboModel that describes the sub-model, and the Name is an actual search path which is describing the sub-model.
Xaml Code:
<Grid VerticalAlignment="Bottom">
<Grid.DataContext>
<comboBoxWhenNoAnySelectedHelpAttempt:ComboboxDataContext/>
</Grid.DataContext>
<StackPanel Orientation="Vertical">
<ComboBox x:Name="ddlCDType"
IsTextSearchEnabled="True"
IsTextSearchCaseSensitive="False"
StaysOpenOnEdit="True"
TextSearch.TextPath="CDType.Name"
Text="{Binding AssignmentText,
UpdateSourceTrigger=LostFocus, Mode=TwoWay,
ValidatesOnDataErrors=True}"
ItemsSource="{Binding CDTypes}"
SelectedValue="{Binding Assignment}"
DisplayMemberPath="CDType.Name"
IsEditable="True"
HorizontalAlignment="Left"
Margin="98,10,0,0"
VerticalAlignment="Top"
Width="136" />
<!--added to see changes in updated combo box item-->
<TextBlock >
<Run Text="Name:"/>
<Run Text="{Binding Assignment.CDType.Name, UpdateSourceTrigger=PropertyChanged}"></Run>
<Run Text="Id:"/>
<Run Text="{Binding Assignment.CDType.ID, UpdateSourceTrigger=PropertyChanged}"></Run>
</TextBlock>
</StackPanel>
</Grid>
VM code
public class ComboboxDataContext:BaseObservableObject
{
private ObservableCollection<ComboModel> _cdTypes;
private ComboModel _assignment;
private string _assignmentText;
private string _lastAcceptedName;
public ComboboxDataContext()
{
CDTypes = new ObservableCollection<ComboModel>
{
new ComboModel
{
CDType = new ComboModelSubModel
{
Name = "Cd-1",
ID = "1",
},
},
new ComboModel
{
CDType = new ComboModelSubModel
{
Name = "Cd-2",
ID = "2",
}
},
new ComboModel
{
CDType = new ComboModelSubModel
{
Name = "Cd-3",
ID = "3",
},
},
new ComboModel
{
CDType = new ComboModelSubModel
{
Name = "Cd-4",
ID = "4",
}
}
};
Assignment = CDTypes.FirstOrDefault();
}
public ObservableCollection<ComboModel> CDTypes
{
get { return _cdTypes; }
set
{
_cdTypes = value;
OnPropertyChanged();
}
}
public ComboModel Assignment
{
get { return _assignment; }
set
{
if (value == null)
_lastAcceptedName = _assignment.CDType.Name;
_assignment = value;
OnPropertyChanged();
}
}
//on lost focus when edit is done will check and update the last edited value
public string AssignmentText
{
get { return _assignmentText; }
set
{
_assignmentText = value;
OnPropertyChanged();
UpDateSourceCollection(AssignmentText);
}
}
//will do the the update on combo lost focus to prevent the
//annessasary updates (each property change will make a lot of noice in combo)
private void UpDateSourceCollection(string assignmentText)
{
var existingModel = CDTypes.FirstOrDefault(model => model.CDType.Name == assignmentText);
if (existingModel != null) return;
if (_lastAcceptedName == null)
{
CDTypes.Add(new ComboModel{CDType = new ComboModelSubModel{ID = string.Empty, Name = assignmentText}});
}
else
{
var existingModelToEdit = CDTypes.FirstOrDefault(model => model.CDType.Name == _lastAcceptedName);
if(existingModelToEdit == null) return;
existingModelToEdit.CDType.Name = assignmentText;
existingModelToEdit.CDType.ID = string.Empty;
}
}
}
public class ComboModel:BaseObservableObject
{
private ComboModelSubModel _cdType;
public ComboModelSubModel CDType
{
get { return _cdType; }
set
{
_cdType = value;
OnPropertyChanged();
}
}
}
public class ComboModelSubModel:BaseObservableObject
{
private string _id;
private string _name;
public string ID
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged();
}
}
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged();
}
}
}
BaseObservableObject code
/// <summary>`enter code here`
/// implements the INotifyPropertyChanged (.net 4.5)
/// </summary>
public class BaseObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
{
var propName = ((MemberExpression)raiser.Body).Member.Name;
OnPropertyChanged(propName);
}
protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(name);
return true;
}
return false;
}
}
Regards.
I've tried solving this myself, looking at several possible solutions here on Stack Overflow, but alas I've been unable to solve this issue.
TL;DR version:
The problem:
A listBox using databinding to show a list of RPG characters, which have a nested property for their attributes. I can't get the attributes to show due to the limitations with nested properties and databindings.
The code below is related to the issue.
I have a listBox that has a databinding that controls what is shown in the list. The databinding uses ObservableCollection for the list of objects that the list contains. All this works fine, but is related to the issue at hand.
The listBox databinding has several nested properties in each element, that I want to display and change in the form, yet I cannot get nested databinding to work correctly.
This is the listBox XAML:
<ListBox x:Name="listCharacters" Margin="2,0" ItemsSource="{Binding}" Grid.Row="1" ScrollViewer.VerticalScrollBarVisibility="Visible" ScrollViewer.HorizontalScrollBarVisibility="Hidden" SelectionChanged="listCharacters_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:Character}" x:Name="Symchar">
<Grid Width="125" HorizontalAlignment="Left" Background="{x:Null}">
<Grid.RowDefinitions>
<RowDefinition Height="18"/>
<RowDefinition Height="12"/>
<RowDefinition Height="16"/>
</Grid.RowDefinitions>
<Image Panel.ZIndex="5" HorizontalAlignment="Right" VerticalAlignment="Top" Height="16" Width="16" Margin="0,2,0,0" Source="Resources/1454889983_cross.png" MouseUp="DeleteCharacter" />
<TextBlock Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" FontWeight="Bold" FontSize="13.333" Grid.Row="0" TextTrimming="CharacterEllipsis" Padding="0,0,16,0" />
<TextBlock Text="{Binding RealRace.Label, UpdateSourceTrigger=PropertyChanged}" FontSize="9.333" Grid.Row="1" FontStyle="Italic" />
<TextBlock FontSize="9.333" Grid.Row="2" HorizontalAlignment="Left" VerticalAlignment="Top">
<Run Text="{Binding RealClass.Archetype.Label, UpdateSourceTrigger=PropertyChanged}"/>
<Run Text=" - "/>
<Run Text="{Binding RealClass.Label, UpdateSourceTrigger=PropertyChanged}"/>
</TextBlock>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And setting the listBox ItemSource:
this.listCharacters.ItemsSource = CharacterList;
This is the character class, I removed unrelated code (XML serialization attributes etc.)
public class Character : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
this.NotifyPropertyChanged("Name");
}
}
private string _player;
public string Player
{
get { return _player; }
set
{
_player = value;
this.NotifyPropertyChanged("Player");
}
}
private string _race;
public string Race
{
get { return _race; }
set
{
_race = value;
this.NotifyPropertyChanged("Race");
}
}
private Race _realRace;
public Race RealRace
{
get { return _realRace; }
set
{
_realRace = value;
Race = value.Id;
this.NotifyPropertyChanged("RealRace");
}
}
private string _gender;
public string Gender
{
get { return _gender; }
set
{
_gender = value;
this.NotifyPropertyChanged("Gender");
}
}
private Attributes _attributes;
public Attributes Attributes
{
get { return _attributes; }
set
{
_attributes = value;
this.NotifyPropertyChanged("Attributes");
}
}
private string _class;
public string Class
{
get { return _class; }
set
{
_class = value;
this.NotifyPropertyChanged("Class");
}
}
private Class _realClass;
public Class RealClass
{
get { return _realClass; }
set
{
_realClass = value;
Class = value.Id;
this.NotifyPropertyChanged("RealClass");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
To keep it simple, the property that I've been testing with, is the 'Attributes' property, this is the code for it:
public class Attributes : INotifyPropertyChanged
{
private int _accurate;
public int Accurate
{
get { return _accurate; }
set
{
_accurate = value;
this.NotifyPropertyChanged("Accurate");
}
}
private int _cunning;
public int Cunning
{
get { return _cunning; }
set
{
_cunning = value;
this.NotifyPropertyChanged("Cunning");
}
}
private int _discreet;
public int Discreet
{
get { return _discreet; }
set
{
_discreet = value;
this.NotifyPropertyChanged("Discreet");
}
}
private int _persuasive;
public int Persuasive
{
get { return _persuasive; }
set
{
_persuasive = value;
this.NotifyPropertyChanged("Persuasive");
}
}
private int _quick;
public int Quick
{
get { return _quick; }
set
{
_quick = value;
this.NotifyPropertyChanged("Quick");
}
}
private int _resolute;
public int Resolute
{
get { return _resolute; }
set
{
_resolute = value;
this.NotifyPropertyChanged("Resolute");
}
}
private int _strong;
public int Strong
{
get { return _strong; }
set
{
_strong = value;
this.NotifyPropertyChanged("Strong");
}
}
private int _vigilant;
public int Vigilant
{
get { return _vigilant; }
set
{
_vigilant = value;
this.NotifyPropertyChanged("Vigilant");
}
}
private int _toughness;
public int Toughness
{
get { return _toughness; }
set
{
_toughness = value;
this.NotifyPropertyChanged("Toughness");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
I want to display each individual attribute in a field when a character in the listBox is selected, this works fine with properties directly in the character class, but due to the limitations on nested properties and databindings, I haven't been able to get it to work with the 'Attributes' properties values.
XAML for one of the attribute input fields:
<TextBox x:Name="attr_Accurate" DataContext="{Binding Path=(local:Character.Attributes), XPath=SelectedItem, ElementName=listCharacters, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Text="{Binding Path=(local:Accurate)}" PreviewTextInput="NumericInput"/>
The UpdateSourceTrigger is simply a method to only allow integers to be input in the field:
private void NumericInput(object sender, TextCompositionEventArgs e)
{
if (!char.IsDigit(e.Text, e.Text.Length - 1))
{
e.Handled = true;
}
}
If anyone could help me get the values within the selected character's attributes to show up via databindings, I would greatly appreciate it.
Use binding as following:
To Show Accurate Property value:
<TextBox x:Name="attr_Accurate" Text="{Binding Path=SelectedItem.Attributes.Accurate), ElementName=listCharacters, Mode=OneWay}" PreviewTextInput="NumericInput"/>
To Show Cunning property value:
<TextBox x:Name="attr_Accurate" Text="{Binding Path=SelectedItem.Attributes.Cunning), ElementName=listCharacters, Mode=OneWay}" PreviewTextInput="NumericInput"/>
and so one.
(I'm not sure if you want binding to be two way or one so please
change as your need)
I'm sure this has already been asked, but I'm still new to MVVM and WPF, and not too sure what I should be searching for.
I have a viewmodel which includes items in a Model, as well as some additional temporary data items which will all be passed to a process.start(). I have a stackpanel of textbox, and want to allow the user to type in a "ModelName", and if existing, The ViewModel will get and set "TemplateName" associated with the ModelName.
I'm a bit lost on how to implement this. Do I need to create a completely separate ViewModel, which then goes and extracts data from ModelViewModel? Do I just write some code under ModelName's set, where it can validate, query, and set TemplateName?
Model:
public partial class Model
{
public string ModelName { get; set; }
public virtual Template Template { get; set; }
and ViewModel, which takes the Model, and some temporary data:
public class LauncherViewModel:ViewModelBase
{
public LauncherViewModel()
{
_ESTContext = new ESTContext();
Models = new ObservableCollection<Model>(_ESTContext.Models);
}
private ESTContext _ESTContext;
private string _modelname;
private string _serialno;
private string _sonumber;
private string _templatename;
private string _outputname;
private Model _selectedmodel;
public ObservableCollection<Model> Models { get; set; }
public string ModelName
{
get { return _modelname; }
set
{
if (!string.Equals(_modelname, value))
{
_modelname = value;
};
}
}
public string TemplateName { get { return _templatename; }}
public string SerialNo { get { return _serialno; } }
public string SONumber { get { return _sonumber; } }
public string OutputName { get { return _outputname; } }
public Model SelectedModel
{
get { return _selectedmodel; }
set
{
if (_selectedmodel != value)
{
_selectedmodel = value;
}
}
}
}
My View:
<DockPanel>
<StackPanel Margin="0,78,0,68" Width="233" DataContext="{Binding Models}">
<ComboBox IsEditable="True" Text="{Binding ModelName}" SelectedItem="{Binding SelectedModel}"/>
<TextBox Height="23" TextWrapping="Wrap" Text="{Binding SONumber}"/>
<TextBox Height="23" TextWrapping="Wrap" Text="{Binding SerialNumber}"/>
<Button Content="Button"/>
</StackPanel>
</DockPanel>
For your gui to update you must implement INotifyPropertyChanged and a call to it on all your bound properties.
// basic base class for your models, you a
public class ModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator] // remove if you are not using R#
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
// your model
public class Model : ModelBase
{
private string modelName;
private Template template;
public string ModelName
{
get { return modelName; }
set
{
if (value == modelName) return;
modelName = value;
OnPropertyChanged();
}
}
public virtual Template Template
{
get { return template; }
set
{
if (Equals(value, template)) return;
template = value;
OnPropertyChanged();
}
}
}
View:
<DockPanel>
<StackPanel Margin="0,78,0,68" Width="233">
<ComboBox IsEditable="True" Text="{Binding ModelName, Mode='TwoWay'}" SelectedItem="{Binding SelectedModel}" ItemsSource="{Binding Models}"/>
<TextBox Height="23" TextWrapping="Wrap" Text="{Binding SONumber Mode='TwoWay'}"/>
<TextBox Height="23" TextWrapping="Wrap" Text="{Binding SerialNumber Mode='TwoWay'}"/>
<Button Content="Button"/>
</StackPanel>
Note the Mode='TwoWay' this makes the GUI change the values in your viewmodel, rather than just display them. So you need to set that on everything that's supposed to be editable as above. The default behaviour of WPF is that when a control looses focus it will update the bound property, if you set UpdateSourceTrigger='PropertyChanged' the property will be updated each time ie a letter is entered in a text box. I'll leave that part to you, but you will have to do it in your vm! At minimum the properties SONumber,SerialNumber and Models(if it's ref. changes).
VM: I assume you use galasoft here
public class LauncherViewModel : ViewModelBase
{
private ESTContext _ESTContext;
private string _templatename;
private string _modelname;
private string serialNumber;
private string _outputname;
private string modelName;
private ObservableCollection<Model> models;
private Model selectedModel;
private string soNumber;
public LauncherViewModel()
{
// dangerous ;)
_ESTContext = new ESTContext();
Models = new ObservableCollection<Model>(_ESTContext.Models);
}
public ObservableCollection<Model> Models
{
get { return models; }
set
{
if (Equals(value, models)) return;
models = value;
RaisePropertyChanged();
}
}
public string ModelName
{
get { return modelName; }
set
{
if (value == modelName) return;
modelName = value;
RaisePropertyChanged();
}
}
public string TemplateName { get { return _templatename; }}
public string SerialNumber // Note you spelled this wrong in your xaml. SONumber
{
get { return serialNumber; }
set
{
if (value == serialNumber) return;
serialNumber = value;
RaisePropertyChanged();
}
}
public string SONumber
{
get { return soNumber; }
set
{
if (value == soNumber) return;
soNumber = value;
RaisePropertyChanged();
}
}
public string OutputName { get { return _outputname; } }
public Model SelectedModel
{
get { return selectedModel; }
set
{
if (Equals(value, selectedModel)) return;
selectedModel = value;
RaisePropertyChanged();
}
}
}
A cool litle thing if you are using galasoft or sim together with R#.
public class YourViewModelBase : ViewModelBase
{
[NotifyPropertyChangedInvocator] // alt + enter = convert auto property to prop
// with backing field and change notification :)
override protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
base.RaisePropertyChanged(propertyName);
}
// .. :)
}
Cheers,
Stian
If I havent misunderstood your question , Bind Models to ItemsSource of ComboBox instead of Binding it to DataContext of StackPanel
Remove DataContext binding
<StackPanel Margin="0,78,0,68" Width="233" >
Bind Itemssource of ComboBox to Models and you will also have to specify DisplayMemberPath to the property of Model that you want to display in ComboBox.
<ComboBox IsEditable="True" ItemsSource="{Binding Models}" Text="{Binding ModelName}" SelectedItem="{Binding SelectedModel}"/>
And I am assuming you are setting DataContext of window to instance of LauncherViewModel class.
You need to raise the property changed event on properties you bind to i.e.:
private string _ModelName;
public string ModelName
{
get { return _ModelName; }
set
{
if (_ModelName != value)
{
_ModelName = value;
RaisePropertyChanged("ModelName");
}
}
}
In your base view model you need something like this (make sure you implement INotifyPropertyChanged):
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
In the constructor or loaded event of your view add this:
DataContext = new LauncherViewModel();
I'm new to MVVM, just recently started my first project following the MVVM pattern. I have an issue trying to validate an ObservableCollection using the IDataErrorInfo Interface. My ObservableCollection looks like this:
ObservableCollection<Magazine> magazineRepository;
public ObservableCollection<Magazine> MagazineRepository
{
get { return magazineRepository; }
set
{
if (value != null)
{
bladRepository = value;
OnPropertyChanged("MagazineRepository");
}
}
}
And my XAML like this:
<ListBox x:Name="listMagazineRepository"
Grid.ColumnSpan="2"
ItemsSource="{Binding}"
DataContext="{Binding MagazineRepository}"
DisplayMemberPath="Navn"
SelectedItem="{Binding Path=SelectedItem}"/>
<TextBox x:Name="txtName" Grid.Row="1" Grid.Column="0"
Text="{Binding ElementName=listMagazineRepository, Path=SelectedItem.Navn, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
<TextBox x:Name="txtPrice" Grid.Row="2" Grid.Column="0"
Text="{Binding ElementName=listMagazineRepository, Path=SelectedItem.Pris, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
It's just a simple listBox containing objects, when you select an item, the selected objects properties is displayed in the textboxes, and is then bound to the listbox object.
My Problem is, that when I set my code up like this, the only way I can figure out how to validate my data is in the Domain Model, which really isn't a good practise, I'd like to validate in the ViewModel before it gets there. Basically I want to validate each property in the MagazineRepository, in the ViewModel, How would you go about doing this?
PS: I'm new to posting on this board (and programming boards in general) if my question is lacking information, please let me know and I will supply the needed details.
Thanks a lot.
If i understand correctly you want to validate the Magazine object. If that's the case, one way to do it is to wrap that class in a viewmodel, let's call it MagazineVM, that implements IDataErrorInfo and keep the magazine object updated. You then bind to the view a list of MagazineVM. As a very simple example:
public class MagazineVM : IDataErrorInfo, INotifyPropertyChanged
{
private Magazine _magazine;
public int FirstMagazineProperty
{
get { return _magazine.FirstMagazineProperty; }
set { _magazine.FirstMagazineProperty = value; RaisePropertyChanged("FirstMagazineProperty"); }
}
//INotifyPropertyChanged implementation
//IDataErrorInfo implementation
}
Firstly, as Dtex says, you should use a MagazineViewModel class rather than a Magazine class. E.G.
public class MagazineViewModel : INotifyPropertyChanged, IDataErrorInfo
{
private string navn;
private string pris;
private string error;
public string Navn
{
get { return navn; }
set
{
if (navn != value)
{
navn = value;
RaisePropertyChanged("Navn");
}
}
}
public string Pris
{
get { return pris; }
set
{
if (pris != value)
{
pris = value;
RaisePropertyChanged("Pris");
}
}
}
public string Error
{
get { return error; }
set
{
if (error != value)
{
error = value;
RaisePropertyChanged("Error");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public string this[string columnName]
{
get
{
var result = string.Empty;
switch (columnName)
{
case "Pris":
if (string.IsNullOrWhiteSpace(Pris))
{
result = "Pris is required";
}
break;
case "Navn":
if (string.IsNullOrWhiteSpace(Navn))
{
result = "Navn is required";
}
break;
}
return result;
}
}
private void RaisePropertyChanged(string PropertyName)
{
var e = PropertyChanged;
if (e != null)
{
e(this, new PropertyChangedEventArgs(PropertyName));
}
}
}
The important property to note is "public string this[string columnName]". ColumnName will be one of your bound properties and this is where you can do validation.
The next thing to consider is your MainViewModel (Your DataContext). E.G.
public class MainViewModel : INotifyPropertyChanged
{
//Use a readonly observable collection. If you need to reset it use the .Clear() method
private readonly ObservableCollection<MagazineViewModel> magazines = new ObservableCollection<MagazineViewModel>();
private MagazineViewModel selectedItem;
//Keep the item being edited separate to the selected item
private MagazineViewModel itemToEdit;
public ObservableCollection<MagazineViewModel> Magazines { get { return magazines; } }
public MagazineViewModel SelectedItem
{
get { return selectedItem; }
set
{
if (selectedItem != value)
{
selectedItem = value;
RaisePropertyChanged("SelectedItem");
//When the selected item changes. Copy it to the ItemToEdit
//This keeps the the copy you are editing separate meaning that invalid data isn't committed back to your original view model
//You will have to copy the changes back to your original view model at some stage)
ItemToEdit = Copy(SelectedItem);
}
}
}
public MagazineViewModel ItemToEdit
{
get { return itemToEdit; }
set
{
if (itemToEdit != value)
{
itemToEdit = value;
RaisePropertyChanged("ItemToEdit");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public MainViewModel()
{
//Ctor...
}
//Create a copy of a MagazineViewModel
private MagazineViewModel Copy(MagazineViewModel ToCopy)
{
var vm = new MagazineViewModel();
vm.Navn = ToCopy.Navn;
vm.Pris = ToCopy.Pris;
return vm;
}
private void RaisePropertyChanged(string PropertyName)
{
//...
}
}
The only thing missing here is how you copy the changes back to the original view model. You could do it before the selected item changes (if the ItemToEdit is valid) or have a Commit button that is only enabled when the ItemToEdit is valid. If you can allow your original view models to go into an invalid state you don't need to worry about the copying.
Finally the XAML
An implicit style to show the error tooltip
<Style
TargetType="{x:Type TextBox}">
<Setter
Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
</Style>
And the controls and bindings
<ListBox
ItemsSource="{Binding Magazines}"
DisplayMemberPath="Navn"
SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}" />
<TextBox
Margin="5"
x:Name="txtName"
Grid.Row="1"
Grid.Column="0"
Text="{Binding ItemToEdit.Navn, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
<TextBox
Margin="5"
x:Name="txtPrice"
Grid.Row="2"
Grid.Column="0"
Text="{Binding ItemToEdit.Pris, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
The TextBoxes bind to ItemToEdit. ItemToEdit will be an in-sync copy of the SelectedItem.
I am just starting with MVVM and have hit a hurdle that I hope someone can help me with. I am trying to create a simple View with 2 listboxes. A selection from the first listbox will populate the second list box. I have a class created that stores the information I want to bind to.
MyObject Class (Observable Object is just a base class that implements INotifyPopertyChanged)
public class MyObject : ObservableObject
{
String _name = String.Empty;
ObservableCollection<MyObject> _subcategories;
public ObservableCollection<MyObject> SubCategories
{
get { return _subcategories; }
set
{
_subcategories = value;
RaisePropertyChanged("SubCategories");
}
}
public String Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged("Name");
}
}
public MyObject()
{
_subcategories = new ObservableCollection<EMSMenuItem>();
}
}
In my viewmodel I have two ObservableCollections created
public ObservableCollection<EMSMenuItem> Level1MenuItems { get; set; }
public ObservableCollection<EMSMenuItem> Level2MenuItems { get; set; }
In my constructor of the ViewModel I have:
this.Level1MenuItems = new ObservableCollection<EMSMenuItem>();
this.Level2MenuItems = new ObservableCollection<EMSMenuItem>();
this.Level1MenuItems = LoadEMSMenuItems("Sample.Xml");
That works fine for the Level1 items and they correctly show in the View. However I have a command that gets called when the user clicks an item in the listbox, which has the following:
Level2MenuItems = ClickedItem.SubCategories;
For some reason this does not update the UI of the second listbox. If I put a breakpoint at this location I can see that Level2MenuItems has the correct information stored in it. If I write a foreach loop and add them individually to the Level2MenuItems collection then it does display correctly.
Also as a test I added the following to the constructor:
Level2MenuItems = Level1MenuItems[0].SubCategories;
And that updated correctly.
So why would the code work as expected in the constructor, or when looping through, but not when a user clicks on an item in the listbox?
You need to raise the change notification on the Level2MenuItems property.
Instead of having
public ObservableCollection<EMSMenuItem> Level2MenuItems { get; set; }
you need
private ObservableCollection<EMSMenuItem> _level2MenuItems;
public ObservableCollection<EMSMenuItem> Level2MenuItems
{
get { return _level2MenuItems; }
set
{
_level2MenuItems = value;
RaisePropertyChanged(nameof(Level2MenuItems));
}
}
The reason the former works in the constructor is that the Binding has not taken place yet. However since you are changing the reference via a command execute which happens after the binding you need to tell view that it changed
You need to make your poco class within the ObservableCollection implement INotifyPropertyChanged.
Example:
<viewModels:LocationsViewModel x:Key="viewModel" />
.
.
.
<ListView
DataContext="{StaticResource viewModel}"
ItemsSource="{Binding Locations}"
IsItemClickEnabled="True"
ItemClick="GroupSection_ItemClick"
ContinuumNavigationTransitionInfo.ExitElementContainer="True">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" Margin="0,0,10,0" Style="{ThemeResource ListViewItemTextBlockStyle}" />
<TextBlock Text="{Binding Latitude, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Style="{ThemeResource ListViewItemTextBlockStyle}" Margin="0,0,5,0"/>
<TextBlock Text="{Binding Longitude, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Style="{ThemeResource ListViewItemTextBlockStyle}" Margin="5,0,0,0" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
public class LocationViewModel : BaseViewModel
{
ObservableCollection<Location> _locations = new ObservableCollection<Location>();
public ObservableCollection<Location> Locations
{
get
{
return _locations;
}
set
{
if (_locations != value)
{
_locations = value;
OnNotifyPropertyChanged();
}
}
}
}
public class Location : BaseViewModel
{
int _locationId = 0;
public int LocationId
{
get
{
return _locationId;
}
set
{
if (_locationId != value)
{
_locationId = value;
OnNotifyPropertyChanged();
}
}
}
string _name = null;
public string Name
{
get
{
return _name;
}
set
{
if (_name != value)
{
_name = value;
OnNotifyPropertyChanged();
}
}
}
float _latitude = 0;
public float Latitude
{
get
{
return _latitude;
}
set
{
if (_latitude != value)
{
_latitude = value;
OnNotifyPropertyChanged();
}
}
}
float _longitude = 0;
public float Longitude
{
get
{
return _longitude;
}
set
{
if (_longitude != value)
{
_longitude = value;
OnNotifyPropertyChanged();
}
}
}
}
public class BaseViewModel : INotifyPropertyChanged
{
#region Events
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected void OnNotifyPropertyChanged([CallerMemberName] string memberName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(memberName));
}
}
}
Your Subcategories property should be read-only.