IDataErrorInfo Issue - Initial property values - c#

i have a ViewModel that implements the IDataErrorInfo interface as usual.
Model
public class Computer : Model
{
public Computer(string name, string ip, string mac, string broadcastIp)
{
Name = name;
Ip = ip;
Mac = mac;
BroadcastIp = broadcastIp;
}
public Computer()
{
Name = "neuer Computer";
Mac = string.Empty;
Ip = string.Empty;
BroadcastIp = string.Empty;
}
[NotMapped]
public ComputerState ComputerState { get; set; }
public string Name { get; set; }
public string Ip { get; set; }
public string Mac { get; set; }
public string BroadcastIp { get; set; }
public virtual Room Room { get; set; }
}
ViewModel
public class ComputerViewModel : ViewModel<Computer>, IListItem
{
private bool _IsSelected;
private ComputerOperationMessage _OperationMessage = ComputerOperationMessage.NO_MESSAGE;
public ComputerViewModel(Computer computer) : base(computer)
{
}
public string Name
{
get { return Model.Name; }
set
{
Model.Name = value;
RaisePropertyChanged(nameof(Name));
}
}
public RoomViewModel Room
{
get { return App.EntityManager.Get<RoomViewModel>().FirstOrDefault(r => r.Model.Equals(Model.Room)); }
set
{
Model.Room = value.Model;
RaisePropertyChanged(nameof(Room));
}
}
public ComputerState State
{
get { return Model.ComputerState; }
set
{
if (value.Equals(Model.ComputerState))
return;
Model.ComputerState = value;
RaisePropertyChanged(nameof(State));
}
}
public string Ip
{
get { return Model.Ip; }
set
{
Model.Ip = value;
RaisePropertyChanged(nameof(Ip));
}
}
public string Mac
{
get { return Model.Mac; }
set
{
Model.Mac = value;
RaisePropertyChanged(nameof(Mac));
}
}
public string BroadcastIp
{
get { return Model.BroadcastIp; }
set
{
Model.BroadcastIp = value;
RaisePropertyChanged(nameof(BroadcastIp));
}
}
/// <summary>
/// UI indicator when a operation like shutdown, reboot, ping etc. is running
/// </summary>
public ComputerOperationMessage OperationMessage
{
get { return _OperationMessage; }
set
{
if (value.Equals(_OperationMessage))
return;
_OperationMessage = value;
RaisePropertyChanged(nameof(OperationMessage));
}
}
public bool IsSelected
{
get { return _IsSelected; }
set
{
_IsSelected = value;
RaisePropertyChanged(nameof(IsSelected));
}
}
public override string ToString()
{
return Name;
}
protected override string ValidateProperty(string property)
{
var computers = App.EntityManager.Get<ComputerViewModel>().ToList();
switch (property)
{
case nameof(Name):
if (string.IsNullOrEmpty(Name))
{
return "Bezeichnung erwartet";
}
if (computers.Any(c => c.Name == Name))
{
return "Bezeichnung bereits vergeben";
}
break;
case nameof(Ip):
if (string.IsNullOrEmpty(Ip))
{
return "Ip erwartet";
}
if (!NetworkCommand.ValidateIp(Ip))
{
return "Ip ungültig";
}
if (computers.Any(c => c.Ip == Ip))
{
return "Ip bereits vergeben";
}
break;
case nameof(Mac):
if (string.IsNullOrEmpty(Mac))
{
return "Mac erwartet";
}
if (!NetworkCommand.ValidateMac(Mac))
{
return "Mac ungültig";
}
if (computers.Any(c => c.Mac == Mac))
{
return "Mac bereits vergeben";
}
break;
case nameof(BroadcastIp):
if (string.IsNullOrEmpty(BroadcastIp))
{
return "Broadcast Ip erwartet";
}
if (!NetworkCommand.ValidateIp(Ip))
{
return "Broadcast Ip ungültig";
}
break;
case nameof(Room):
if (Room == null)
return "Raum wählen";
break;
}
return string.Empty;
}
public abstract class ViewModel : INotifyPropertyChanged, IDataErrorInfo
{
private string _ErrorMessage;
private bool _HasErrors;
public ViewModel()
{
SaveCommand = new RelayCommand(SaveAction);
}
public bool HasErrors
{
get { return _HasErrors; }
set
{
_HasErrors = value;
RaisePropertyChanged(nameof(HasErrors));
}
}
public ICommand SaveCommand { get; }
public Model Model { get; protected set; }
public string this[string property]
{
get
{
_ErrorMessage = ValidateProperty(property);
RaisePropertyChanged(nameof(Error));
HasErrors = !string.IsNullOrEmpty(Error);
return Error;
}
}
public string Error
{
get { return _ErrorMessage; }
}
public event PropertyChangedEventHandler PropertyChanged;
private void SaveAction(object obj)
{
if (Model != null && Model.Id == 0)
{
App.EntityManager.Add(Model);
}
App.EntityManager.Save();
}
protected virtual string ValidateProperty(string property)
{
return string.Empty;
}
protected void RaisePropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
public abstract class ViewModel<T> : ViewModel where T : Model
{
protected ViewModel(T model)
{
Model = model;
base.Model = model;
}
public new T Model { get; }
public long Id => Model.Id;
}
XAML:
<Label Content="{StaticResource NameString}" />
<TextBox Text="{Binding Path=Name, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
<Label Content="{StaticResource IpString}" />
<TextBox Text="{Binding Path=Ip,ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
<Label Content="{StaticResource MacString}" />
<TextBox Text="{Binding Path=Mac, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
<Label Content="{StaticResource BroadcastIpString}" />
<TextBox Text="{Binding Path=BroadcastIp, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
<Label Content="{StaticResource RoomString}" />
<ComboBox ItemsSource="{Binding Path=Rooms}" SelectedItem="{Binding Path=Room, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
<GridSplitter Height="25" />
<Button x:Name="SaveButton"
HorizontalAlignment="Right"
Command="{Binding Path=SaveCommand}"
Content="{StaticResource SaveIcon}"
IsEnabled="{Binding Path=CanSave,
Converter={StaticResource TrueToFalseConverter},
UpdateSourceTrigger=PropertyChanged}" />
Calling code in class that implements ICommand interface:
var viewModel = parameter as ComputerViewModel;
_Editor = new ComputerEditor();
_Editor.CloseButtonCommand = new RelayCommand(CloseCommand);
_Editor.SaveButton.Click += (s, e) =>
{
App.EntityManager.Add(_Editor.ComputerEditViewModel.Model);
App.EntityManager.Save();
_Editor.IsOpen = false;
(Application.Current.MainWindow.Content as Grid).Effect = null;
};
if (viewModel == null)
{
_Editor.DataContext = new ComputerEditViewModel(new Computer());
_Editor.Title = Application.Current.FindResource("ComputerAddString").ToString();
}
else
{
_Editor.DataContext = new ComputerEditViewModel(viewModel.Model);
_Editor.Title = Application.Current.FindResource("ComputerEditString").ToString();
}
OpenChildWindow(_Editor);
My problem:
When i create a new entity without any initial values the IDataErrorInfo is executed all the time but the error (red border and label) is only shown when the new entity properties has valid initial values.
Control images
The (1) is the initial controlstate. The first textbox ('Name') has a default value that comes from the entity contructor. All other values (Textboxes) are just empty.
At this point my validation is working in my viewmodel for all textboxes/properties but only the first textbox get the red border and label when the validation fails, cause of the valid start value!
For the other properties i have to set a valid (initial) value before the red border and label is shown (when validation fails, cause of property change) although i check if the textbox/property is empty.
As i said, the validation is working in the viewmodel all the time but the view only shows the red border and label when the initial value is valid against my implementation.
Hoping this question is not to weird.
Feel free to ask anything if something is just unclear
Best regards
Dustin

Related

Populating a WPF ListBox based on a ComboBox Selection

I am trying to populate a WPF ListBox with data from a SQL Stored Procedure based on a ComboBox Selection. I've gotten the ComboBox to work like its supposed to, but I can't get the ListBox to display any data. My naming might be a little weird, but think of it as: the ComboBox gets all Recipes from SQL and the ListBox needs to display a list of Ingredients and their Amounts based on the users selection from that ComboBox. The API and Stored Procedures(...GetAll() for the ComboBox and GetByRationId() for the ListBox...) work, as I can retrieve the correct data using Swagger in the API and I can Populate the ComboBox and the RationId TextBlock in the UI, but I can't get the ListBox to show any data. I am still new to programming and I'm following tutorials etc. and I can't seem to find anything that speaks to my case specifically. I'm guessing I'm missing something. I've added the aforementioned TextBlock just to display the RationId, which is what needs to be used to get the correct data from SQL, as a test, just to make sure that the Id was getting through...and it is.
Here's the Xaml...
<StackPanel Grid.Column="1" Margin="50" Orientation="Vertical">
<ComboBox x:Name="FeedGroup" MinWidth="300" MinHeight="50"
SelectedItem="{Binding SelectedFeedGroup}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding FeedGroupName}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock x:Name="SelectedFeedGroup_RationId" Height="81"/>
<ListBox x:Name="FeedGroupRation" MinHeight="200" Padding="20" ItemsSource="{Binding SelectedFeedGroupRation}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="10" HorizontalAlignment="Center">
<TextBlock Text="{Binding CommodityName}" FontSize="20" FontWeight="Bold"
VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBlock Text="{Binding CommodityPercentage}" FontSize="16" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
Here is the ViewModel Class...
public class FeedGroupPageViewModel : Screen
{
IFeedGroupEndPoint _feedGroupEndPoint;
IFeedGroupRationEndPoint _feedGroupRationEndPoint;
IMapper _mapper;
private readonly StatusInfoViewModel _status;
private readonly IWindowManager _window;
public FeedGroupPageViewModel(IFeedGroupEndPoint feedGroupEndPoint,
IFeedGroupRationEndPoint feedGroupRationEndpoint,
IConfigHelper configHelper,
IMapper mapper,
StatusInfoViewModel status,
IWindowManager window)
{
_feedGroupEndPoint = feedGroupEndPoint;
_feedGroupRationEndPoint = feedGroupRationEndpoint;
_configHelper = configHelper;
_mapper = mapper;
_status = status;
_window = window;
}
protected override async void OnViewLoaded(object view)
{
base.OnViewLoaded(view);
try
{
await LoadFeedGroup();
}
catch (Exception ex)
{
}
}
private async Task LoadFeedGroup()
{
var FeedGroupList = await _feedGroupEndPoint.GetAll();
var feedGroup = _mapper.Map<List<FeedGroupDisplayModel>>(FeedGroupList);
FeedGroup = new BindableCollection<FeedGroupDisplayModel>(feedGroup);
}
private BindableCollection<FeedGroupDisplayModel> _feedGroup;
public BindableCollection<FeedGroupDisplayModel> FeedGroup
{
get { return _feedGroup; }
set
{
_feedGroup = value;
NotifyOfPropertyChange(() => FeedGroup);
}
}
private FeedGroupDisplayModel _selectedFeedGroup;
public FeedGroupDisplayModel SelectedFeedGroup
{
get { return _selectedFeedGroup; }
set
{
_selectedFeedGroup = value;
NotifyOfPropertyChange(() => SelectedFeedGroup);
}
}
private BindableCollection<FeedGroupRationModel> _feedGroupRation;
public BindableCollection<FeedGroupRationModel> FeedGroupRation
{
get { return _feedGroupRation; }
set
{
_feedGroupRation = value;
NotifyOfPropertyChange(() => FeedGroupRation);
}
}
private BindableCollection<FeedGroupRationModel> _selectedFeedGroupRation;
public BindableCollection<FeedGroupRationModel> SelectedFeedGroupRation
{
get { return _selectedFeedGroupRation; }
set
{
_selectedFeedGroupRation = value;
NotifyOfPropertyChange(() => SelectedFeedGroupRation);
}
}
}
And here are the Model Classes
public class FeedGroupDisplayModel : INotifyPropertyChanged
{
public int Id { get; set; }
public string UserId { get; set; }
public string FeedGroupName { get; set; }
public DateTime CreateDate { get; set; }
public DateTime LastModified { get; set; }
public int RationId { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void CallPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class FeedGroupRationModel : INotifyPropertyChanged
{
public int Id { get; set; }
public string UserId { get; set; }
public int RationId { get; set; }
public string RationName { get; set; }
public int CommodityId { get; set; }
public string CommodityName { get; set; }
public int CommodityPercentage { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void CallPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And Here are My Endpoint Classes
public class FeedGroupEndPoint : IFeedGroupEndPoint
{
private IAPIHelper _apiHelper;
public FeedGroupEndPoint(IAPIHelper apiHelper)
{
_apiHelper = apiHelper;
}
public async Task<List<FeedGroupModel>> GetAll()
{
using (HttpResponseMessage response = await _apiHelper.ApiClient.GetAsync("/api/FeedGroup"))
{
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsAsync<List<FeedGroupModel>>();
return result;
}
else
{
throw new Exception(response.ReasonPhrase);
}
}
}
}
and
public class FeedGroupRationEndPoint : IFeedGroupRationEndPoint
{
private IAPIHelper _apiHelper;
public FeedGroupRationEndPoint(IAPIHelper apiHelper)
{
_apiHelper = apiHelper;
}
public async Task<List<FeedGroupRationModel>> GetRationById()
{
using (HttpResponseMessage response = await _apiHelper.ApiClient.GetAsync("/api/FeedGroup"))
{
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsAsync<List<FeedGroupRationModel>>();
return result;
}
else
{
throw new Exception(response.ReasonPhrase);
}
}
}
}
I can add more info if needed. I've been working on this for quite awhile now and I'm just out of ideas. Any help would be greatly appreciated!
Thanks in advance!!
You don't seem to set the FeedGroupRation that the ListBox binds to somewhere.
I guess you want to fetch the items and set the property when the SelectedFeedGroup property is set. You could then hook up an event handler to the PropertyChanged event or override the NotifyOfPropertyChange method. Something like this:
public override async void NotifyOfPropertyChange([CallerMemberName] string propertyName = null)
{
base.NotifyOfPropertyChange(propertyName);
if (propertyName == nameof(FeedGroup))
{
//get the items...
var results = await ...;
//set the source property
FeedGroupRation = results;
}
}
As #Michal Davis comment stated I was missing a method for loading the ration, so I added LoadFeedGroupRation()...
private async Task LoadFeedGroupRation()
{
var _feedGroupRation = await _feedGroupRationEndPoint.GetRation();
var feedGroupRation = _mapper.Map<List<FeedGroupRationDisplayModel>>
(_feedGroupPenList);
FeedGroupRationList = new BindableCollection<FeedGroupRationDisplayModel>
(feedGroupRation);
}
Also based on #EldHasp's comment I updated the SelectedFeedGroup setter...
public FeedGroupDisplayModel SelectedFeedGroup
{
get { return _selectedFeedGroup; }
set
{
_selectedFeedGroup = value;
var FeedGroupRation = LoadFeedGroup
NotifyOfPropertyChange(() => SelectedFeedGroup);
}
}
I Don't know if this was the best way but I worked for my case.

Update Bool Property Based on INotifyDataErrorInfo in WPF C#

In my Previous Question, I got a solution how to manually check the Validation Result https://stackoverflow.com/questions/39031783/screen-validation-with-data-annotations-on-button-click-in-using-inotifydataerro/39033124#39033124
I'm having a Model Person, it contains three properties FirstName (required), MiddleName (optional) and LastName (required).
Now I changed My Code slightly
public class PersonViewModel : BaseViewModel, INotifyDataErrorInfo {
private string _personObj = string.Empty;
public Person PersonObj
{
get { return _personObj; }
set { _personObj= value; OnPropertyChanged(); }
}
public bool IsValidObject
{
get
{
var context = new ValidationContext(EmpInfo, null, null);
bool flag = Validator.TryValidateObject(EmpInfo, context, null, true);
return flag;
}
}
public ICommand SaveDataCommand
{
get
{
return new DelegatingCommand(SaveData);
}
}
public void SaveData
{
// Validate Data;
}
}
The Model Class: Person
public class Person
{
[Required]
public string FirstName { get; set; }
public string MiddleName { get; set; }
[Required]
public string LastName { get; set; }
}
My XAML is
<TextBox Text="{Binding PersonObj.FirstName, UpdateSourceTrigger=PropertyChanged,
,ValidatesOnNotifyDataErrors=True}" />
<TextBox Text="{Binding PersonObj.MiddleName, UpdateSourceTrigger=PropertyChanged,
,ValidatesOnNotifyDataErrors=True}" />
<TextBox Text="{Binding PersonObj.LastName, UpdateSourceTrigger=PropertyChanged,
,ValidatesOnNotifyDataErrors=True}" />
<Button Content="Save" IsDefault="True" IsEnabled="{Binding IsValidObject}" Command="{Binding SaveDataCommand}" />
The Property IsValidObject Initially works fine. If I update the Value in FirstName and LastName, then the property IsValidObject is not updating the result in UI.
I used one more approach
public class PersonViewModel : BaseViewModel, INotifyDataErrorInfo {
private string _personObj = string.Empty;
public Person PersonObj
{
get { return _personObj; }
set { _personObj= value; OnPropertyChanged(); }
}
public bool IsValidObject
{
get
{
var context = new ValidationContext(EmpInfo, null, null);
bool flag = Validator.TryValidateObject(EmpInfo, context, null, true);
return flag;
}
}
public ICommand SaveDataCommand
{
get
{
return new DelegatingCommand(SaveData, ValidateEmployeeObject);
}
}
public bool ValidateEmployeeObject()
{
var context = new ValidationContext(EmpInfo, null, null);
bool flag = Validator.TryValidateObject(EmpInfo, context, ErrorResult, true);
return flag;
}
public void SaveData
{
// Validate Data;
}
}
Here I introduced a Method ValidateEmployeeObject(), it returns TRUE / FALSE as similar to the Property IsValidObject. I binded this Method in CanExecute Block of SaveDataCommand as proposed by #adminSoftDK in the comment part. This approach too fails...
How could I update the Save Button to make Enable based on the above validation.
If you try something like this
public PersonViewModel()
{
SaveDataCommand = new DelegatingCommand(SaveData, ()=> flag);
}
bool flag;
public bool IsValidObject
{
get
{
var context = new ValidationContext(EmpInfo, null, null);
flag = Validator.TryValidateObject(EmpInfo, context, null, true);
SaveDataCommand.RaiseCanExecute() // don't know what type of command you have but you should have a method which you can call something with Canexecute
return flag;
}
}
public bool ValidateEmployeeObject()
{
return IsValidObject;
}
public ICommand SaveDataCommand { get; }
I'm not very familiar with INotifyDataErrorInfo and I don't know at what point is your IsValidObject is called, but i think something like the above should work.

XAML nested template binding

I have a list view where I bind multiple data templates to using a DataTemplateSelector. However, I cannot build my project, I'm getting an error of "Object reference not set to an instance of an object". I narrowed down the issue to the data binding ItemsSource of the combobox. If I comment out the combobox, my project will build. As I understand, the Binding keyword evaluates at runtime, but this is a compile time error. I tried to get around the error by swapping out to x:Bind, as that evaluates at compile-time but then that required the type of the DataTemplate to be defined.
x:DataType={x:Type templates:FilterDropdownDataTemplate}
However, x:Type isn't defined in a Windows 10 universal app. Any suggestions?
Here is my XAML:
<Page.Resources>
<DataTemplate x:Name="DateComboboxTemplate">
<TextBlock Text="{Binding}"/>
</DataTemplate>
<DataTemplate x:Name="FilterDropdownDataTemplate">
<StackPanel>
<TextBlock Text="{Binding Value}"/>
<ComboBox ItemsSource="{Binding DateRanges}"
SelectionChanged="{Binding DateSelection}"
SelectedItem="{Binding SelectedDateRange, Mode=TwoWay}"
ItemTemplate="{StaticResource DateComboboxTemplate}">
</ComboBox>
</StackPanel>
</DataTemplate>
<config:MainFilterTemplateSelector
x:Name="MainFilterSelector"
FilterDropdownTemplate="{StaticResource FilterDropdownDataTemplate}"
/>
</Page.Resources>
<ListView x:Name="FilterCategories"
Margin="0"
Padding="0"
BorderThickness="0"
ItemsSource="{Binding Filters}"
SelectedItem="{Binding SelectedFilterCategory, Mode=TwoWay}"
IsItemClickEnabled="True"
SelectionChanged="{x:Bind ViewModel.OpenSubFilters}"
ItemTemplateSelector="{StaticResource MainFilterSelector}">
</ListView>
And here is the relevant C# code (I'm using a MVVM pattern):
public class MainFilterViewDropdownListTemplate : Filter
{
private ObservableCollection<string> _dateRanges;
private string _selectedDateRange;
public MainFilterViewDropdownListTemplate() : base()
{
_dateRanges = new ObservableCollection<string>();
}
public ObservableCollection<string> DateRanges
{
get
{
return _dateRanges;
}
set
{
SetProperty(ref _dateRanges, value);
// hard coding these 4 lines for testing for now
_dateRanges.Add("Last 7 days");
_dateRanges.Add("Last 14 days");
_dateRanges.Add("Last 30 days");
_dateRanges.Add("Custom");
}
}
public string SelectedDateRange
{
get
{
return _selectedDateRange;
}
set
{
SetProperty(ref _selectedDateRange, value);
}
}
public IEnumerable<Filter> AllFilters { get; set; }
public void InitializeFields(Filter filter)
{
Column = filter.Column;
DisplayType = filter.DisplayType;
ID = filter.ID;
IsSelected = filter.IsSelected;
ParentId = filter.ParentId;
SelectedCount = filter.SelectedCount;
TotalCount = filter.TotalCount;
Value = filter.Value;
visibility = filter.visibility;
}
private void DateSelection()
{
}
}
And
public class MainFilterTemplateSelector : DataTemplateSelector
{
public DataTemplate FilterDropdownTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object selector)
{
if (selector == null)
{
throw new ArgumentNullException("Template is null");
}
if (!(selector is FilterBase))
{
throw new ArgumentException("This list can only be populated by a class that extends Filter.");
}
else
{
var filterType = selector as Filter;
switch (filterType.DisplayType)
{
case "date":
return FilterDropdownTemplate;
default:
return base.SelectTemplateCore(selector);
}
}
}
}
And
public class Filter : FilterBase
{
private int _selectedCount;
private int _totalCount;
private Visibility _visibility;
public IEnumerable<Filter> Children
{
get;
set;
}
public int TotalCount
{
get
{
return _totalCount;
}
set
{
SetProperty(ref _totalCount, value);
}
}
public int SelectedCount
{
get
{
return _selectedCount;
}
set
{
SetProperty(ref _selectedCount, value);
}
}
public Visibility visibility
{
get
{
return _visibility;
}
set
{
SetProperty(ref _visibility, value);
}
}
public void InitializeAllChildren(IEnumerable<Filter> allFilters)
{
foreach (Filter item in allFilters)
{
item.Children = allFilters.GetAllChildrenFiltersForParentID(item.ID) as IEnumerable<Filter>;
if (item.Children == null)
{
item.TotalCount = 0;
}
else
{
item.TotalCount = item.Children.Count();
item.SelectedCount = item.Children.Where(t => t.IsSelected.Value).Count();
}
}
Children = allFilters.GetAllChildrenFiltersForParentID(ID);
}
}
And
public class FilterBase : ViewModelBase
{
public int ID
{
get
{
return _id;
}
set
{
SetProperty(ref _id, value);
}
}
public string Value
{
get
{
return _value;
}
set
{
SetProperty(ref _value, value);
}
}
public string Column
{
get
{
return _column;
}
set
{
SetProperty(ref _column, value);
}
}
public int ParentId
{
get
{
return _parentId;
}
set
{
SetProperty(ref _parentId, value);
}
}
public string DisplayType
{
get
{
return _displayType;
}
set
{
SetProperty(ref _displayType, value);
}
}
public bool? IsSelected
{
get
{
return _isSelected;
}
set
{
SetProperty(ref _isSelected, value);
}
}
private int _id;
private string _value;
private string _column;
private int _parentId;
private string _displayType;
private bool? _isSelected;
}
And (BindableBase is from the Prism.Mvvm namespace)
public class ViewModelBase : BindableBase, IDisposable
{
PropertyChangeActionHelper _propertyChangeActionHelper;
protected void RegisterPropertyChangeAction(Action action, params string[] monitoredProperties)
{
if (_propertyChangeActionHelper == null)
{
_propertyChangeActionHelper = new PropertyChangeActionHelper(this);
}
_propertyChangeActionHelper.RegisterPropertyChangeAction(action, monitoredProperties);
}
#region IDisposable Support
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (_propertyChangeActionHelper != null)
{
_propertyChangeActionHelper.Dispose();
_propertyChangeActionHelper = null;
}
}
}
~ViewModelBase()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(false);
}
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
Assuming that the templates namespace is declared and FilterDropdownDataTemplate is a type within that namespace, you should be able to define the DataTemplate's data type without x:Type using the following syntax:
<DataTemplate x:DataType="templates:FilterDropdownDataTemplate">

How to combine two ObservableCollection to bind dynamic buttons

This is using C# and MVVM.
At the moment I have a bunch of buttons that are created on to a panel via binding an ObservableCollection<Module> called ModuleCollection.
module is defined as:
public string ModuleName { get; private set; }
public string ModuleAbbreviation { get; private set; }
public bool ModuleDisabled { get; private set; }
public DateTime ModuleLicenseDate { get; private set; }
A label below each button gets set to ModuleName and the Content property of the button gets set to ModuleAbbreviation.
I also have a current_user object that holds a ObservableCollection<UserModule> called UserModules
A UserModule is defined as:
public int Module_ID { get; set; }
public int User_Module_Access { get; set; }
Using the ModuleCollection and current_user.UserModules lists, I would like to enable the buttons under the follow scenarios:
If the Module.Disabled = false
and the Module.ModuleLicenseDate > Now()
and the UserModule.User_Module_Access > 0
Otherwise the button will be disabled.
Other things to note: is that UserModules may only have a subset of ModuleCollection and ModuleCollection will be static but the properties within UserModules will be refreshed from time to time.
My question is: How to I go about binding these two collections so that I can them create my buttons and set the IsEnabled property based on both?
[EDIT] 2013-12-07
<Button.IsEnabled>
<MultiBinding Converter="{Binding viewmodel:IsEnabledMultiValueConverter}">
<Binding Source="{Binding ModuleID}" />
<Binding Source="{Binding ModuleDisabled}" />
<Binding Source="{Binding ModuleLicenseDate}" />
<Binding Source="{Binding current_user.user_modules}" />
</MultiBinding>
</Button.IsEnabled>
[EDIT2] 2013-12-09
When I change the module access level in a user module the event is getting fired. I change the value from accessible(1) to unaccessible(0), so in-turn the button should get disabled, however this does not happen.
public class UserModule : INotifyPropertyChanged
{
public UserModule(int Module_ID, int User_Module_Access)
{
this.Module_ID = Module_ID;
this.User_Module_Access = User_Module_Access;
}
private int _userModuleAccess;
public int Module_ID { get; set; }
public int User_Module_Access
{
get { return _userModuleAccess; }
set
{
_userModuleAccess = value;
MessageBox.Show("User_Module_Access");
RaisePropertyChanged("User_Module_Access");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
My thoughts are, that an event for ObservableCollection<UserModule> needs to occur when an item property changes. I have read that ObservableCollections don't do this, only adding, deleting and moving items. How to do this?
Some how in the User class?
public class User
{
public string UserName { get; set; }
public ObservableCollection<UserModule> UserModules = new ObservableCollection<UserModule>();
}
[Edit3] 2013-12-10 Redo - Implementing ItemsObservableObservableCollection
Launcher.XAML
<Button Content="{Binding ModuleAbbreviation}"
Style="{DynamicResource LauncherButton}"
Background="{Binding ModuleColor}"
FontSize="32" FontFamily="Tahoma" Width="130" Height="100"
Command="{Binding DataContext.LaunchCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding ModuleName}">
<Button.Resources>
<viewmodel:IsEnabledMultiValueConverter x:Key="converter" />
</Button.Resources>
<Button.IsEnabled>
<MultiBinding Converter="{StaticResource converter}">
<Binding Path="ModuleID" />
<Binding Path="ModuleEnabled" />
<Binding Path="ModuleLicenseDate" />
<Binding ElementName="gridModules" Path="DataContext.CurrentUser" />
</MultiBinding>
</Button.IsEnabled>
</Button>
LauncherViewModel.cs
class LauncherViewModel
{
public LauncherViewModel()
{
timer = new Timer(SetModuleAccess, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
}
int pass = 0;
bool _isEnabled = false;
Timer timer;
private void SetModuleAccess(object state)
{
if (pass > 0)
{
if (_isEnabled)
_isEnabled = false;
else
_isEnabled = true;
foreach (Users.UserModule uModule in ups.model.ups_repository._current_user.UserModules)
{
if (uModule.Module_ID == 0)
{
if (_isEnabled == false)
uModule.User_Module_Access = 0;
else
uModule.User_Module_Access = 1;
}
}
if (pass == 2)
ups.model.ups_repository._current_user.UserModules.Add(new Users.UserModule(8, 1));
}
pass++;
}
public Users.User CurrentUser
{
get { return ups.model.ups_repository._current_user; }
}
public ObservableCollection<Module> ModuleCollection
{
get { return ModuleKey._module_objects; }
}
}
public class IsEnabledMultiValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
try
{
bool userHasAccess = false;
int ModuleID = (int)values[0];
bool ModuleEnabled = (bool)values[1];
string ModuleLicenseDate = (string)values[2];
Users.User user = values[3] as Users.User;
Users.UserModule userModule = user.UserModules.SingleOrDefault(um => um.Module_ID == ModuleID);
DateTimeFormatInfo dtfi = new DateTimeFormatInfo();
dtfi.ShortDatePattern = "yyyy-MM-dd";
dtfi.DateSeparator = "-";
DateTime MLicenseDate = System.Convert.ToDateTime(ModuleLicenseDate, dtfi);
if (userModule != null)
{
userHasAccess = userModule.User_Module_Access > 0;
}
return (ModuleEnabled && (MLicenseDate >= DateTime.Now) && userHasAccess);
}
catch
{
return false;
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
User.cs
public class User : INotifyPropertyChanged
{
public static User CreateNewUser()
{
return new User();
}
public User() {}
public int User_ID { get; set; }
public string Username { get; set; }
public string Name { get; set; }
public string Job_Title { get; set; }
public string Department { get; set; }
public string Company { get; set; }
public string Phone_Office { get; set; }
public string Phone_Mobile { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public DateTime Last_Login { get; set; }
public int Status { get; set; }
public int Session_Timeout { get; set; }
private ItemsObservableObservableCollection<UserModule> user_modules = new ItemsObservableObservableCollection<UserModule>();
public ItemsObservableObservableCollection<UserModule> UserModules
{
get
{
return user_modules;
}
set
{
user_modules = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
UserModule.cs
public class UserModule : INotifyPropertyChanged
{
public UserModule(int Module_ID)
{
this.Module_ID = Module_ID;
}
public UserModule(int Module_ID, int User_Module_Access)
{
this.Module_ID = Module_ID;
this.User_Module_Access = User_Module_Access;
}
private int _module_id;
private int _userModuleAccess;
public int Module_ID
{
get { return _module_id; }
set
{
_module_id = value;
RaisePropertyChanged("Module_ID");
}
}
public int User_Module_Access
{
get { return _userModuleAccess; }
set
{
_userModuleAccess = value;
RaisePropertyChanged("User_Module_Access");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
ups.cs
namespace ups.model
{
public static class ups_repository
{
public static Users.User _current_user = new Users.User();
public static void LoadUser(string Username, string Key, string Message)
{
...
}
}
}
At this point buttons are displaying, in the VM above I switch the first user module in the collection from enabled to disabled, each five seconds. I also add permission for the last in the collection. The buttons are not enabled and disabling the way they should.
You can try MultiBinding with MultiValueConverter on IsEnabled property of Button. The MultiValueConverter will take the Module bound to the Button; and the Module Collection of the current user (in the following example I sent the user itself) and do the testing to return a bool to indicate if the Button can be enabled or disabled.
<Window x:Class="WpfHowTo.ItemsControlTestHorn"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:WpfHowTo"
Title="ItemsControlTestHorn" Height="300" Width="300">
<Window.Resources>
<l:MultiValueConverter x:Key="multiValueConverter"/>
</Window.Resources>
<Grid Name="gridModules">
<ItemsControl ItemsSource="{Binding Path=Modules}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Button Content="{Binding ModuleAbbreviation}">
<Button.IsEnabled>
<MultiBinding Converter="{StaticResource multiValueConverter}">
<Binding Path="."/>
<Binding ElementName="gridModules" Path="DataContext.CurrentUser"/>
<!--<Binding RelativeSource="{RelativeSource AncestorType=Grid}" Path="DataContext.CurrentUser"/>-->
</MultiBinding>
</Button.IsEnabled>
</Button>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
The MultiValueConverter is something like this.
public class MultiValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool result = true;
Module module = values[0] as Module;
User user = values[1] as User;
bool userHasAccess = false;
UserModule userModule = user.UserModules.SingleOrDefault(um => um.Module_ID == module.Module_ID);
if (userModule != null)
{
userHasAccess = userModule.User_Module_Access == 1;
}
return result = ! module.ModuleDisabled && module.ModuleLicenseDate > DateTime.Now && userHasAccess;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Class definitions
public class Module
{
public int Module_ID { get; set; }
public string ModuleName { get; set; }
public string ModuleAbbreviation { get; set; }
public bool ModuleDisabled { get; set; }
public DateTime ModuleLicenseDate { get; set; }
}
public class UserModule
{
public int Module_ID { get; set; }
public int User_Module_Access { get; set; }
}
public class User
{
private ObservableCollection<UserModule> _userModules = new ObservableCollection<UserModule>();
public string UserName { get; set; }
public ObservableCollection<UserModule> UserModules
{
get
{
return _userModules;
}
}
}
I have come up with one more idea which is more simpler. However, I am leaving the first answer as an example for MultiBinding.
Here goes the new idea. I have created a new collection using the other two collections. This technique is now in line with the title of the question.
public IEnumerable<object> Modules
{
get
{
ObservableCollection<Module> modules = GetAllModules();
User currentUser = GetCurrentUser();
var accessibleModules = modules.GroupJoin
(
currentUser.UserModules, m => m.Module_ID, um => um.Module_ID,
(m, um) => new
{
ModuleName = m.ModuleName,
ModuleAbbreviation = m.ModuleAbbreviation,
IsModuleAccessible = !m.ModuleDisabled && m.ModuleLicenseDate > DateTime.Now && (um.Count() == 0 ? -1 : um.Single().User_Module_Access) == 1
}
);
return accessibleModules;
}
}
In the .xaml
<ItemsControl ItemsSource="{Binding Path=Modules}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Button Content="{Binding ModuleAbbreviation}" IsEnabled="{Binding IsModuleAccessible}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
ItemsObservableObservableCollection
The following class is intended to overcome the problem that ObservableCollection does not listen to the changes of the contained objects.
The pre-requisite is that the objects which are being added to this collection should implement INotifyPropertyChanged interface.
In short, this class registers to the items' PropertyChanged event and rises the CollectionChanged event of the ObservableCollection.
http://msdn.microsoft.com/en-us/magazine/dd252944.aspx
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Collections;
namespace VJ.Collections
{
/// <summary>
/// This class adds the ability to refresh the list when any property of
/// the objects changes in the list which implements the INotifyPropertyChanged.
///
/// </summary>
/// <typeparam name="T">
/// The type of elements in the collection.
/// </typeparam>
public class ItemsObservableObsrvableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
RegisterPropertyChanged(e.NewItems);
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
UnRegisterPropertyChanged(e.OldItems);
}
else if (e.Action == NotifyCollectionChangedAction.Replace)
{
UnRegisterPropertyChanged(e.OldItems);
RegisterPropertyChanged(e.NewItems);
}
base.OnCollectionChanged(e);
}
protected override void ClearItems()
{
UnRegisterPropertyChanged(this);
base.ClearItems();
}
private void RegisterPropertyChanged(IList items)
{
foreach (INotifyPropertyChanged item in items)
{
item.PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
}
}
private void UnRegisterPropertyChanged(IList items)
{
foreach (INotifyPropertyChanged item in items)
{
item.PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
}
}
private void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
}

How to add Item to ComboBox from UI?

Ok maybe i'm a little bit stupid but i can't find a way to Add an Item to my Combobox and get it as SelectedItem. So what do i miss?
Current Code
LoginV.XAML
<ComboBox Height="23" Margin="102,2,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" Width="155"
IsEditable="True"
DisplayMemberPath="Loginname"
ItemsSource ="{Binding alleBenutzer}"
SelectedItem="{Binding selectedBenutzer}"/>
LoginVM.CS
public List<User> alleBenutzer{ get; set; }
public User selectedBenutzer
{
get { return _selectedBenutzer; }
set
{
_selectedBenutzer = value;
if (selectedBenutzer != null)
{
//do stuff
}
RaisePropertyChanged(() => Reg(() => benutzerEinrichtungen));
}
}
User.cs
public class User
{
public int Id { get; set; }
public string Loginname { get; set; }
}
Summary
How can i provide following behavior?
User runes the App added the Word "Admin" in the Combobox Control which will result in an SelectedItem != null so that i can do if(selectedBenutzer.Loginname =="Admin") DoStuff;
Use ObservableCollection instead of List for alleBenutzer:
private ObservableCollection<string> _alleBenutzer;
public ObservableCollection<string> alleBenutzer
{
get
{
return _alleBenutzer;
}
set
{
_alleBenutzer= value;
RaisePropertyChanged("alleBenutzer");
}
}
Add a button Add user.
Add AddUser() method to your ViewModel:
public void AddUser()
{
alleBenutzer.Add(new User {...});
}
Assign a RelayCommand to Click event of the button.
Assign the same RelayCommand to AddUser() method of your ViewModel
ok after some further research it turns out that i need to bind to the Text Property which contains the Value i was looking for
here an simple example
XAML
<ComboBox Height="23" HorizontalAlignment="Left" Margin="89,23,0,0" Name="comboBox1" VerticalAlignment="Top" Width="120"
IsEditable="True"
ItemsSource="{Binding mySimpleItems}"
SelectedItem="{Binding mySimpleItem}"
Text="{Binding myNewSimpleItem}"/>
Code
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new SimpleVM();
}
}
public class SimpleVM
{
private string _mySimpleItem;
private string _myNewSimpleItem;
private bool isNew = true;
#region properties
public ObservableCollection<string> mySimpleItems { get; set; }
public string mySimpleItem
{
get { return _mySimpleItem; }
set
{
_mySimpleItem = value;
if (_mySimpleItem != null)
{
isNew = false;
MessageBox.Show(_mySimpleItem);
}
else
isNew = true;
}
}
public string myNewSimpleItem
{
get { return _myNewSimpleItem; }
set
{
_myNewSimpleItem = value;
//if SelectedItem == null
if (isNew)
if (_myNewSimpleItem == "Super")
{
mySimpleItem = _myNewSimpleItem;
mySimpleItems.Add(_myNewSimpleItem);
}
}
}
#endregion
#region cTor
public SimpleVM()
{
var ObCol = new ObservableCollection<string>();
ObCol.Add("Max");
ObCol.Add("Dennis");
ObCol.Add("Lucas");
mySimpleItems = ObCol;
}
#endregion
}

Categories

Resources