I am trying to build my application settings page depending on the properties exposed by ViewModel. I'm using .Net 4.0 with MVVM. ViewModel exposes single Collection of "group of setting values". Group represents properties that are dependent on each other and belong to a logical group wrt to domain. Settings page in view is created using DataTemplate like below:-
<DataTemplate x:Key="contentSettingGroup1">
<TextBlock Text="{Binding Field1Description}" />
<TextBox Text="{Binding Field1Value, Mode=TwoWay}" Grid.Column="2" />
<TextBlock Text="{Binding Field2Description}" />
<TextBox Text="{Binding Field2Value, Mode=TwoWay}" Grid.Column="6" />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SettingGroup1}">
<HeaderedContentControl Header="{Binding}" HeaderTemplate="{StaticResource titleArea}" Content="{Binding}" ContentTemplate="{StaticResource contentSettingGroup1}" />
</DataTemplate>
Then I have a class in ViewModel module to represent the "Group of settings" as below:
public class SettingGroup1 : INotifyPropertyChanged, IDataErrorInfo
{
public double Field1value { get; private set; }
public double Field2value { get; private set; }
private double mField1;
public double Field1value
{
get { return mField1; }
set
{
if (mField1 != value)
{
mField1 = value;
RaisePropertyChanged(() => Field1value);
}
}
}
private double mField2;
public double Field2value
{
get { return mField2; }
set
{
if (mField2 != value)
{
mField2 = value;
RaisePropertyChanged(() => Field2value);
}
}
}
public string Error
{
get { return null; }
}
public string this[string property]
{
get
{
string errorMsg = null;
switch (property)
{
case "Field1value":
if (Field1value < 0.0)
{
errorMsg = "The entered value of Field1 is invalid !";
}
if (Field1value < Field2value)
{
errorMsg = "The Field1 should be greater than Field2 !";
}
break;
}
return errorMsg;
}
}
}
And finally the viewModel exposes collection of such group of settings:
public ObservableCollection<object> Settings
{
get
{
var pageContents = new ObservableCollection<object>();
var group1 = new SettingGroup1();
group1.Field1.Description = "Description value 1";
group1.Field1.Value = mValue1;
group1.Field2.Description = "Description value 2";
group1.Field2.Value = mValue2;
pageContents.Add(group1);
// add other groups of controls
// ...
return pageContents;
}
}
The Problem: The property setter is called but Data validation is not called whenever UI value is changed. I've tried putting IDataErrorInfo implementation in ViewModel class also but none works. I've to use group of settings as these application settings are used in many projects and we don't want duplicate XAML for each application.
Note: The viewmodel is not exposing the property that UI is binding to e.g. Field1Value but exposing an encapsulated object.
You aren't telling your view that the property you're binding to needs to be validated. Use "ValidatesOnDataErrors = true" in your binding.
<TextBox Text="{Binding Field1Value, Mode=TwoWay, ValidatesOnDataErrors=True}" Grid.Column="2" />
Related
I have a ListBox control with TypeUsers.When I select some record in Listbox and update Name in TextBox the Name property/textbox return always null. Never take value from TextBox, always null ?
Image description here
This is my code
<ListBox x:Name="LstTypeUsers"
Grid.Row="0" Grid.Column="4"
Width="220" Height="120"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ItemsSource="{Binding TypeUsers}"
DisplayMemberPath="Name">
</ListBox>
<TextBox
Grid.Row="0" Grid.Column="2"
x:Name="txtName"
HorizontalAlignment="Left" Height="23"
TextWrapping="Wrap"
VerticalAlignment="Top" Width="170"
Text="{Binding ElementName=LstTypeUsers, Path=SelectedItem.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"
Validation.ErrorTemplate="{x:Null}"/>
<Button
Grid.Column="0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="100" Height="30"
Command="{Binding UpdateTypeUserCmd}"
Grid.ColumnSpan="3" Margin="20,90,0,0">
<StackPanel Orientation="Horizontal">
<Image Source="/Images/Save.png" />
<TextBlock Width="55" Height="18" ><Run Text=" "/><Run Text="Update"/></TextBlock>
</StackPanel>
</Button>
EDIT
// Model class
public class UserType: INotifyPropertyChanged
{
[Key]
private int usertypeId;
public int UserTypeId
{
get
{
return this.usertypeId;
}
set
{
this.usertypeId = value;
OnPropertyChanged("UserTypeId");
}
}
[MaxLength(200)]
private string name;
public string Name
{
get
{
return this.name;
}
set
{
this.name = value;
OnPropertyChanged("Name");
}
}
[Required]
private bool status;
public bool Status
{
get
{
return this.status;
}
set
{
this.status = value;
OnPropertyChanged("Status");
}
}
public virtual ObservableCollection<User> User { get; private set; }
public UserType()
{
this.User = new ObservableCollection<User>();
}
}
// ViewModelBase class
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
// UserTypeViewModel
public class UserTypeViewModel
private UserType _userType;
private ObservableCollection<UserType> _UserTypeList;
// Constructor
public UserTypeViewModel()
{
_userType = new UserType();
_UserTypeList = new ObservableCollection<UserType>(GetUserTypeAll());
}
public ObservableCollection<TypeUsers> TypeUsers
{
get
{
return _UserTypeList;
}
set
{
_UserTypeList = value;
//OnPropertyChanged("TypeUsers");
}
}
public string Name
{
get
{
return _userType.Name;
}
set
{
_userType.Name = value;
//OnPropertyChanged("Name");
}
}
Thank you.
Implement INotifyPropertyChanged interface in UserType class.
You're binding directly to the WPF control (ListBox) and not the ViewModel. I suggest you add a property in your ViewModel that will bind to the TextBox.Text property, once the data changes or the user had changed the value in the TextBox, then the data will update and be reflected in the UI.
Also, if I remember correctly, at launch, the SelectedItem property of the ListBox is null, so there might be a problem there too, but I'm not certain about that...
I have resolved my problem. I have also implemented INotifyPropertyChanged interface in Model class. I'm new in WPF MVVM and I read that this interface is implemented only in the ViewModel for connection with View. Now I have implemented in both classes and everything works great.
Thanks Everyone.
I have strange WPF ObservableCollection behavior. By unclear reason when collection modified and there is another condition in getter-method in some property of my class, it does't modify View. Although CollectionChanged event was invoked!
Without condition in getter method collection works good.
Too complicated and long-winded to explain here what I do in my work project. Therefore I make small simplify project and emulate same behavior. This project show you problem better then thousand words.
To see the problem - launch project as it is, looks how it works. It is really simple, 2 radiobuttons, datagrid and nothing else. Then go to the MainWindowViewModel, GridItems-property, uncomment commented code and launch project again. See the difference. When collection modify, get-method of GridItems-property dont't invoke (I check it with debugger). How not invoked method can make affect on something??? I don't have any idea on it. Help plz.
Project link:
http://www.megafileupload.com/en/file/443850/ObservableCollection-zip.html
class MainWindowViewModel : ViewModelBase
{
private ObservableCollection<GridItem> _totalStorage;
private ObservableCollection<GridItem> _gridItems;
public ObservableCollection<GridItem> GridItems
{
get
{
//if (_gridItems.Count == 0)
//{
// return _totalStorage;
//}
return _gridItems;
}
set
{
_gridItems = value;
RaisePropertyChanged("GridItems");
}
}
public MainWindowViewModel()
{
_totalStorage = new ObservableCollection<GridItem>();
_gridItems = new ObservableCollection<GridItem>();
GridItemsInit();
_gridItems.CollectionChanged += CollectionChanged;
}
/// <summary>
/// Collection change event handler
/// </summary>
/// <param name="o"></param>
/// <param name="e"></param>
private void CollectionChanged(object o, NotifyCollectionChangedEventArgs e)
{
}
private void GridItemsInit()
{
_totalStorage.Add(new GridItem
{
Name = "Igor",
LastName = "Balachtin",
FilerField = FileStatusEnum.All
});
_totalStorage.Add(new GridItem
{
Name = "Misha",
LastName = "Ivanov",
FilerField = FileStatusEnum.All
});
_totalStorage.Add(new GridItem
{
Name = "Ahmed",
LastName = "Magamed",
FilerField = FileStatusEnum.All
});
_totalStorage.Add(new GridItem
{
Name = "abrek",
LastName = "cheburek",
FilerField = FileStatusEnum.All
});
_totalStorage.Add(new GridItem
{
Name = "Irka",
LastName = "Dirka",
FilerField = FileStatusEnum.All
});
}
private void RefreshGridSource(string statusParam)
{
_gridItems.Clear();
//Если нажали на баттон new
if (statusParam.Equals(FileStatusEnum.All.ToString()))
{
foreach (var item in _totalStorage)
{
_gridItems.Add(item);
}
}
//Если нажали на archived
else if (statusParam.Equals(FileStatusEnum.Filtered.ToString()))
{
foreach (var item in _totalStorage.Where(g => g.FilerField == FileStatusEnum.Filtered))
{
_gridItems.Add(item);
}
}
}
private RelayCommand<object> _radioCommand;
public RelayCommand<object> RadioCommand
{
get { return _radioCommand ?? (_radioCommand = new RelayCommand<object>(HandlerFileRadio)); }
}
private void HandlerFileRadio(object obj)
{
if (obj == null)
return;
var statusParam = obj.ToString();
RefreshGridSource(statusParam);
}
}
Look at this sample.
//if (_gridItems.Count == 0)
//{
// return _totalStorage;
//}
Model:
public enum FileStatusEnum
{
All = 0,
Filtered
}
public class GridItem
{
public String Name { get; set; }
public String LastName { get; set; }
public FileStatusEnum FilerField
{
get; set;
}
}
Xaml:
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="3*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<RadioButton Margin="5" IsChecked="True" Command="{Binding RadioCommand}"
CommandParameter="All">All</RadioButton>
<RadioButton Margin="5" Command="{Binding RadioCommand}"
CommandParameter="Filtered">Filtered</RadioButton>
</StackPanel>
<DataGrid Grid.Column="1" ItemsSource="{Binding GridItems}" CanUserAddRows="False" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" IsReadOnly="True" />
<DataGridTextColumn Header="LastName" Binding="{Binding LastName}" IsReadOnly="True" />
</DataGrid.Columns>
</DataGrid>
</Grid>
really better post all code here instead of link? :/
Once you are changing record after that your property is not Notifying.so please Notify after changing the collection.
Add below line in RefreshGridSource method after collection changed .
RaisePropertyChanged("GridItems");
Your GridItems property can return either _totalStorage or _gridItems depending upon a condition; _totalStorage and _gridSettings are two different instances of ObservableCollection. Initially, when _gridItems has no items, your GridItems property returns _totalStorage to WPF, and WPF listens on this instance for any CollectionChanged events.
In your RefreshGridSource method you are updating _gridItems (a differnt instance from _totalStorage), which WPF has no knowledge of.
But, when you rasie property changed for GridItems property from RefreshGridSource method WPF will re-read the property GridItems - this time, WPF gets _gridItems collection and it works as you expected.
Hope, I have explained well enough.
I have a sample MVVM WPF application and I'm having problems creating DataTemplates for my dynamically loaded model. Let me try explain:
I have the following simplified classes as part of my Model, which I'm loading dynamically
public class Relationship
{
public string Category { get; set; }
public ParticipantsType Participants { get; set; }
}
public class ParticipantsType
{
public ObservableCollection<ParticipantType> Participant { get; set; }
}
public class ParticipantType
{
}
public class EmployeeParticipant : ParticipantType
{
public EmployeeIdentityType Employee { get; set; }
}
public class DepartmentParticipant : ParticipantType
{
public DepartmentIdentityType Department { get; set; }
}
public class EmployeeIdentityType
{
public string ID { get; set; }
}
public class DepartmentIdentityType
{
public string ID { get; set; }
}
Here is how my View Model looks like. I created a generic object Model property to expose my Model:
public class MainViewModel : ViewModelBase<MainViewModel>
{
public MainViewModel()
{
SetMockModel();
}
private void SetMockModel()
{
Relationship rel = new Relationship();
rel.Category = "213";
EmployeeParticipant emp = new EmployeeParticipant();
emp.Employee = new EmployeeIdentityType();
emp.Employee.ID = "222";
DepartmentParticipant dep = new DepartmentParticipant();
dep.Department = new DepartmentIdentityType();
dep.Department.ID = "444";
rel.Participants = new ParticipantsType() { Participant = new ObservableCollection<ParticipantType>() };
rel.Participants.Participant.Add(emp);
rel.Participants.Participant.Add(dep);
Model = rel;
}
private object _Model;
public object Model
{
get { return _Model; }
set
{
_Model = value;
NotifyPropertyChanged(m => m.Model);
}
}
}
Then I tried creating a ListBox to display specifically the Participants Collection:
<ListBox ItemsSource="{Binding Path=Model.Participants.Participant}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Expander Header="IdentityFields">
<!-- WHAT TO PUT HERE IF PARTICIPANTS HAVE DIFFERENT PROPERTY NAMES -->
</Expander>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
The problem is:
I don't know how to create a template that can handle both type of ParticipantTypes, in this case I could have EmployeeParticipant or DepartmentParticipant so depending on that, the data binding Path would be set to Employee or Department properties accordingly
I though about creating a DataTemplate for each type (e.g. x:Type EmployeeParticipant) but the problem is that my classes in my model are loaded dynamically at runtime so VisualStudio will complain that those types don't exist in the current solution.
How could I represent this data in a ListBox then if my concrete types are not known at compile time, but only at runtime?
EDIT: Added my test ViewModel class
You can still create a DataTemplate for each type but instead of using DataType declarations to have them automatically resolve you can create a DataTemplateSelector with a property for each template (assigned from StaticResource in XAML) that can cast the incoming data item to the base class and check properties or otherwise determine which template to use at runtime. Assign that selector to ListBox.ItemTemplateSelector and you'll get similar behavior to what DataType would give you.
That's not a good view-model. Your view-model should be view-centric, not business-centric. So make a class that can handle all four cases from a visual perspective, then bridge your business classes over to that view-model.
EDIT:
Working off your code:
<ListBox ItemsSource="{Binding Path=Model.Participants}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Expander Header="IdentityFields">
<TextBlock Text={Binding Id} />
<TextBlock Text={Binding Name} />
</Expander>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I changed the binding, I assume that was a mistake?
I would create a ViewModel for Participant:
public class Participant_VM : ViewModelBase
{
private string _name = string.Empty;
public string Name
{
get
{
return _name ;
}
set
{
if (_name == value)
{
return;
}
_name = value;
RaisePropertyChanged(() => Name);
}
private string _id= string.Empty;
public string Id
{
get
{
return _id;
}
set
{
if (_id== value)
{
return;
}
_id = value;
RaisePropertyChanged(() => Id);
}
}
}
Modify the ListBox as follows.
<ListBox ItemsSource="{Binding Model.Participants.Participant}">
<ListBox.Resources>
<DataTemplate DataType="{x:Type loc:DepartmentParticipant}">
<Grid>
<TextBlock Text="{Binding Department.ID}"/>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type loc:EmployeeParticipant}">
<Grid>
<TextBlock Text="{Binding Employee.ID}"/>
</Grid>
</DataTemplate>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Expander Header="IdentityFields">
<!-- WHAT TO PUT HERE IF PARTICIPANTS HAVE DIFFERENT PROPERTY NAMES -->
<ContentPresenter Content="{Binding }"/>
</Expander>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Edit:
loc refers to the namespace in which the DepartmentParticipant and EmployeeParticipant are present. Hope you are familiar with adding namespaces.
i have a combox control which is bound to a property using MVVM. There is validation done in the set method on value change.. The problem is the value getting changed to new value even if the validation fails and not retaining the old value..
Below is the XAML:
<ComboBox Grid.Column="1" Grid.Row="1" Width="200" ItemsSource="{Binding Path=Applications, Mode=OneTime}" SelectedItem="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=DataContext.Application, Mode=TwoWay}" Margin="3"></ComboBox>
Below is the View Model Code:
private string[] types = new string[] { "A", "B" };
private string application;
public ObservableCollection<string> Applications { get; private set; }
public Const() {
this.Applications = new ObservableCollection<string>(this.types.ToList());
}
public string Application {
get {
this.application = this.applicationSpecificRequirements.ContainsKey(Resources.ApplicationKey) ? this.applicationSpecificRequirements[Resources.ApplicationKey] : this.Applications[0];
return this.application;
}
set {
if (this.exchangeViewModel.CheckIfApplicationNameExistsOrIsEmptyAndAssign(this.InstanceName, value)) {
System.Windows.Application.Current.Dispatcher.BeginInvoke(
new Action(() => {
this.applicationSpecificRequirements[Resources.ApplicationKey] = this.application;
((IHaveOnPropertyChangedMethod) this).OnPropertyChanged("Application");
}), DispatcherPriority.ContextIdle, null);
return;
}
this.applicationSpecificRequirements[Resources.ApplicationKey] = value;
}
}
looks like you're missing OnPropertyChanged(...) at last line in property setter.
I'm having an issue with an ObservableCollection getting new items but not reflecting those changes in a ListView. I have enough quirks in the way I'm implementing this that I'm having a hard time determining what the problem is.
My ObservableCollection is implemented thusly:
public class MessageList : ObservableCollection<LobbyMessage>
{
public MessageList(): base()
{
Add(new LobbyMessage() { Name = "System", Message = "Welcome!" });
}
}
I store the collection in a static property (so that its easily accessible from multiple user controls):
static public MessageList LobbyMessages { get; set; }
In the OnLoad event of my main NavigationWindow I have the following line:
ChatHelper.LobbyMessages = new MessageList();
My XAML in the UserControl where the ListView is located reads as:
<ListBox IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Mode=OneWay}"
x:Name="ListBoxChatMessages"
d:UseSampleData="True"
ItemTemplate="{DynamicResource MessageListTemplate}"
IsEnabled="True">
<ListBox.DataContext>
<Magrathea_Words_Tools:MessageList/>
</ListBox.DataContext>
</ListBox>
The initial message that I added in the constructor appears in the UI just fine.
Now, the way I add new items to the collection is from a CallBack coming from a WCF service. I had this code working in a WinForms application and it was neccessary to marshall the callback to the UI thread so I left that code in place. Here is an abbreviated version of the method:
Helper.Context = SynchronizationContext.Current;
#region IServiceMessageCallback Members
/// <summary>
/// Callback handler for when the service has a message for
/// this client
/// </summary>
/// <param name="serviceMessage"></param>
public void OnReceivedServiceMessage(ServiceMessage serviceMessage)
{
// This is being called from the WCF service on it's own thread so
// we have to marshall the call back to this thread.
SendOrPostCallback callback = delegate
{
switch (serviceMessage.MessageType)
{
case MessageType.ChatMessage:
ChatHelper.LobbyMessages.Add(
new LobbyMessage()
{
Name = serviceMessage.OriginatingPlayer.Name,
Message = serviceMessage.Message
});
break;
default:
break;
}
};
Helper.Context.Post(callback, null);
}
While debugging I can see the collection getting updated with messages from the service but the UI is not reflecting those additions.
Any ideas about what I'm missing to get the ListView to reflect those new items in the collection?
I resolved this issue.
Neither the static property or the context of the incoming data had anything to do with the issue (which seems obvious in hindsight).
The XAML which was generated from Expression Blend was not up to the task for some reason. All I did to get this to work was assign the ItemSource to the collection in C#.
ListBoxChatMessages.ItemsSource = ChatHelper.LobbyMessages.Messages;
My XAML is now more simplified.
<ListBox IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Mode=OneWay}" Background="#FF1F1F1F"
Margin="223,18.084,15.957,67.787" x:Name="ListBoxChatMessages"
ItemTemplate="{DynamicResource MessageListTemplate}"
IsEnabled="True"/>
I'm a little confused as to why this works. I was reading the MSDN articles on how to bind data in WPF and they included several binding objects, referencing properties on object, etc. I don't understand why they went to all the trouble when one line of code in the UserControl's constructor does the trick just fine.
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));
}
}
}