ItemsSource to ObservableCollection<T> doesn't refresh if change items inside it - c#

In my application I have a datagrid which has as its ItemsSource ObservableCollection< Carrello >.
<Grid x:Name="DatiCarrello">
<DataGrid Name="carrello" RowStyle="{StaticResource RowStyleWithAlternation}" AlternationCount="2" AutoGenerateColumns="False" Margin="40,95,250,30">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding Path=ID_prodotto}" />
<DataGridTextColumn Header="Descrizione" Binding="{Binding Path=Descrizione}" Width="100"/>
[...]
</DataGrid.Columns>
</DataGrid>
</Grid>
My class Carrello:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace WpfApplication1.Classi
{
class Carrello
{
public string ID_prodotto { get; set; }
public string Descrizione { get; set; }
public double Prezzo { get; set; }
public int Quantita { get; set; }
public int Sconto { get; set; }
public int Quantita_massima { get; set; }
private static ObservableCollection<Carrello> ProdottiInCarrello = new ObservableCollection<Carrello>();
public static void ClearElencoProdottiInCarrello()
{
ProdottiInCarrello.Clear();
}
public static ObservableCollection<Carrello> GetElencoProdottiInCarrello()
{
return ProdottiInCarrello;
}
public static void InserciProdottoInCarrello(Carrello items)
{
foreach (Carrello element in ProdottiInCarrello)
if (element.ID_prodotto == items.ID_prodotto)
{
element.Quantita += items.Quantita;
return;
}
ProdottiInCarrello.Add(items);
}
}
}
And this it how I use it:
public partial class FinestraCassa : UserControl
{
private Carrello prodotto_carrello = new Carrello();
public FinestraCassa()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
DatiCarrello.DataContext = prodotto_carrello;
Carrello.ClearElencoProdottiInCarrello();
carrello.ItemsSource = Carrello.GetElencoProdottiInCarrello();
}
private void qta_articolo_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
int sconto = 0;
int.TryParse(sconto_articolo.Text.Replace("%", ""), out sconto);
prodotto_carrello.Sconto = sconto;
Carrello.InserciProdottoInCarrello(prodotto_carrello);
/* Pulisco per nuovo elemento */
prodotto_carrello = new Carrello();
DatiCarrello.DataContext = prodotto_carrello;
TextBoxSearch.Focus();
}
}
}
For every new product that I insert the DataGrid is properly notified, and displays the new line in it.
The problem is when I insert the same product, it should update only the amount (if it is already in the list). Effectively amount is updated, but the refresh is not performed immediately, but I have to click inside the cell "Quantity" to see the change.
Should I implement the INotifyPropertyChanged, but I can not understand how ...

In order to pick up the property changes that you make to your 'Carello' class in the background, you should implement it like this...
public class Carrello :INotifyPropertyChanged
{
private string _id_prodotto;
public string ID_prodotto
{
get { return _id_prodotto; }
set
{
if (value != _id_prodotto)
{
_id_prodotto = value;
OnPropertyChanged("ID_prodotto");
}
}
}
//
// Do the same thing for all the other public properties you are binding to
//
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = System.Threading.Interlocked.CompareExchange(ref
PropertyChanged, null, null);
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
This will raise the appropriate notification, and that will result in the behaviour you are looking for.

Related

ObservableCollection<T> doesn't update the GUI

I really don't know how to get this working. I have an ObservableCollection with "BackupItems" in it. This collection is shown in a ListView. When I add/remove or change the "BackupItems" the User Interface doesn't update! Can you help me please? Have I set the Binding wrong? :(
This is my code: (shortened to the relevant things)
BackupWindow XAML:
<ListView ItemsSource="{Binding Path=BackupItems}" SelectedItem="{Binding Path=SelectedBackupItem}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=Name}"></TextBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Location">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding Path=Location"} />
<Button Grid.Column="1" Content="Browse..." Click="BrowseButton_Click"></Button>
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Date of last Backup" DisplayMemberBinding="{Binding Path=LastBackup}" />
<GridViewColumn Header="Date Created" DisplayMemberBinding="{Binding Path=Created}" />
<GridViewColumn Header="Date Modified" DisplayMemberBinding="{Binding Path=Modified}" />
</GridView>
</ListView.View>
</ListView>
Code Behind:
public partial class BackupWindow : Window
{
public BackupWindow()
{
InitializeComponent();
BackupViewModel backupViewModel = new BackupViewModel();
DataContext = backupViewModel;
}
private void CreateBackupItemButton_Click(object sender, RoutedEventArgs e)
{
BackupViewModel backupViewModel = (BackupViewModel)DataContext;
BackupItem newBackupItem = new BackupItem();
newBackupItem.Created = DateTime.Now;
newBackupItem.Modified = DateTime.Now;
newBackupItem.Name = "";
newBackupItem.Location = "";
backupViewModel.BackupItems.Add(newBackupItem);
}
private void DeleteBackupItemButton_Click(object sender, RoutedEventArgs e)
{
BackupViewModel backupViewModel = (BackupViewModel)DataContext;
backupViewModel.BackupItems.Remove(backupViewModel.SelectedBackupItem);
}
private void BrowseButton_Click(object sender, RoutedEventArgs e)
{
UIElement browseButton = (UIElement)sender;
DependencyObject grid = VisualTreeHelper.GetParent(browseButton);
DependencyObject currentbackupItem = VisualTreeHelper.GetParent(grid);
System.Windows.Controls.ContentPresenter a = (System.Windows.Controls.ContentPresenter)currentbackupItem;
BackupItem currentBackupItem = (BackupItem)a.Content;
BackupViewModel backupViewModel = (BackupViewModel)DataContext;
// The VistaFolderBrowserDialog is from "Ooki.Dialogs"
VistaFolderBrowserDialog folderBrowserDialog = new VistaFolderBrowserDialog();
bool? result = folderBrowserDialog.ShowDialog();
if (result == true) // If the user presses Open in the dialog
{
// Find the currentBackupItem in the List
for (int i = 0; i < backupViewModel.BackupItems.Count; i++)
{
if (currentBackupItem == backupViewModel.BackupItems[i])
{
backupViewModel.BackupItems[i].Location = folderBrowserDialog.SelectedPath;
backupViewModel.BackupItems[i].Modified = DateTime.Now;
break;
}
}
}
}
}
ViewModel
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
BackupViewModel:
public class BackupViewModel : ViewModel
{
public ObservableCollection<BackupItem> BackupItems
{
get
{
return BackupItemLibrary.GetInstance().BackupItems;
}
}
}
BackupItemLibrary
public class BackupItemLibrary
{
private static BackupItemLibrary instance;
public static BackupItemLibrary GetInstance()
{
if (instance == null)
{
instance = new BackupItemLibrary();
}
return instance;
}
public static void SetInstance(BackupItemLibrary newBackupItemLibrary)
{
instance = newBackupItemLibrary;
}
private BackupItemLibrary()
{
}
public string FileName { get; set; }
private ObservableCollection<BackupItem> backupItems = new ObservableCollection<BackupItem>();
public ObservableCollection<BackupItem> BackupItems
{
get
{
return backupItems;
}
set
{
backupItems = value;
}
}
BackupItem:
public class BackupItem
{
public string Name { get; set; }
public string Location { get; set; }
public DateTime LastBackup { get; set; }
public DateTime Created { get; set; }
public DateTime Modified { get; set; }
}
The solution for the problem:
Class BackupItem must implement INotifyPropertyChanged (e.g by also deriving from ViewModel), and fire the PropertyChanged event when a property value is set (at least when the Location or Modified properties are set).
Code:
public class BackupItem : INotifyPropertyChanged
{
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged("Name");
}
}
private string location;
public string Location
{
get
{
return location;
}
set
{
location = value;
OnPropertyChanged("Location");
}
}
private DateTime lastBackup;
public DateTime LastBackup
{
get
{
return lastBackup;
}
set
{
lastBackup = value;
OnPropertyChanged("LastBackup");
}
}
private DateTime created;
public DateTime Created
{
get
{
return created;
}
set
{
created = value;
OnPropertyChanged("Created");
}
}
private DateTime modified;
public DateTime Modified
{
get
{
return modified;
}
set
{
modified = value;
OnPropertyChanged("Modified");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Why DataGrid data is not updated when i change the data in the ObservableCollection?

I have a small DataGrid to do a simple operation. Fields are 3: Number 1, 2 and Result Numer.
DataGrid code is as follows:
<DataGrid x:Name="dgNumbers" ItemsSource="{Binding lstOperations, Mode=TwoWay}" CanUserAddRows="True" AutoGenerateColumns="False" CellEditEnding="dgNumbers_CellEditEnding">
<DataGrid.Columns>
<DataGridTextColumn Header="Number 1" Width="*" Binding="{Binding N1, Mode=TwoWay}"/>
<DataGridTextColumn Header="Number 2" Width="*" Binding="{Binding N2, Mode=TwoWay}"/>
<DataGridTextColumn Header="Result" Width="*" Binding="{Binding Result, Mode=TwoWay}" IsReadOnly="True"/>
</DataGrid.Columns>
</DataGrid>
I created an object where I keep the number 1, the number and outcome. This is the class code:
public class Numbers
{
public decimal N1 { get; set; }
public decimal N2 { get; set; }
public decimal Result { get; set; }
}
I made this small example to try to understand the Binding that make the ObservableCollection.
For this example, I have the following code in the event:
public MainWindow()
{
InitializeComponent();
lstOperations = new ObservableCollection<Numbers>();
}
ObservableCollection<Numbers> lstOperations;
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Numbers n = new Numbers();
n.N1 = 10;
n.N2 = 5;
n.Result = 15;
lstOperations.Add(n);
dgNumbers.ItemsSource = lstOperations;
}
private void dgNumbers_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
foreach(var item in lstOperations)
{
item.Result = item.N1 + item.N2;
}
}
What I am trying to know if changing a data collection, this fact is reflected in the DataGrid, if possible, how to do it right ?, if not possible, how to achieve something similar?
You can also install by NuGet Prism.Core and use the BindableBase class:
using Prism.Mvvm;
using System.Collections.ObjectModel;
using System.Windows;
namespace YourApplication
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
private void dgNumbers_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
foreach (var item in (DataContext as MainWindowViewModel).LstOperations)
{
item.Result = item.N1 + item.N2;
}
}
}
public class MainWindowViewModel : BindableBase
{
private ObservableCollection<Numbers> _lstOperations;
public ObservableCollection<Numbers> LstOperations
{
get { return _lstOperations; }
set
{
_lstOperations = value;
OnPropertyChanged();
}
}
public MainWindowViewModel()
{
_lstOperations = new ObservableCollection<Numbers>();
Numbers n = new Numbers
{
N1 = 10,
N2 = 5,
Result = 15
};
LstOperations.Add(n);
}
}
public class Numbers : BindableBase
{
private decimal _n1;
public decimal N1
{
get { return _n1; }
set { SetProperty(ref _n1, value); }
}
private decimal _n2;
public decimal N2
{
get { return _n2; }
set { SetProperty(ref _n2, value); }
}
private decimal _result;
public decimal Result
{
get { return _result; }
set { SetProperty(ref _result, value); }
}
}
}
In the end you also have to change the Binding in the View:
<DataGrid x:Name="dgNumbers" ItemsSource="{Binding LstOperations, Mode=TwoWay}" CanUserAddRows="True" AutoGenerateColumns="False" CellEditEnding="dgNumbers_CellEditEnding">
Using BindableBase is quite easy because it takes the CallerMemberName attribute in the implementation, so you don't have to specify which property is being called (you write OnPropertyChanged() in the setter instead of OnPropertyChanged("propertyName")). And it's even better, because there is also the SetProperty method, which sets the new value AND calls OnPropertyChanged event only when needed (when the new value is really new, really changed).
Do you have a class which implements INotifyPropertyChanged?
You can subscribe to ObservableCollection events. CollectionChange would probably do the trick but doesn't take advantage of data binding.
public MainWindow()
{
InitializeComponent();
lstOperations = new ObservableCollection<Numbers>();
lstOperations.CollectionChanged += MyCollectionChanged;
}
private MyCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
dgNumbers.ItemsSource = lstOperations;
}
So the basic data binding would go like this.
public class ModelView : INotifyPropertyChanged
{
public ModelView()
{
lstOperations = new ObservableCollection<Numbers>();
lstOperations.CollectionChanged += new NotifyCollectionChangedEventHandler((obj, e) => { OnPropertyChanged("lstOperations "); });
}
//----------------- Implementing the interface here
public event PropertyChangedEventHandler PropertyChanged;
// Call this method when you want the GUI updated.
public void OnPropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
//-------------------Your Properties-----------------------------
private ObservableCollection<Numbers> _lstOperations ;
public ObservableCollection<Numbers> lstOperations
{
get{return _lstOperations ;}
set
{
_lstOperations = value;
OnPropertyChanged("lstOperations");
}
}
Ok the class above now contains your variable you want to bind. Now you need to set your datacontext for the Datagrid.
// Need your model instance.
private ModelView Model;
public MainWindow()
{
InitializeComponent();
Model = new ModelView();
dgNumbers.DataContext = Model;
}
Now anywhere you are manipulating lstOperations you manipulate Model.lstOperations.
forgive my stupid tip but for me it works when i bind such a list
public class ModelNumber
{
public decimal N1 { get; set; }
public decimal N2 { get; set; }
public decimal Result { get; set; }
}
public class ViewModelNumber : NotifyPropertyChanged, IDataErrorInfo
{
protected ModelNumber __dataModel = null;
#region ---constructor---
public ViewModelNumber()
{
// model init
this.__dataModel = new ModelNumber();
}
#endregion
#region ---accessoren model basis---
public decimal N1
{
get
{
return this.__dataModel.N1;
}
set
{
if (this.__dataModel.N1 != value)
{
this.__dataModel.N1 = value;
this.OnPropertyChanged("N1");
}
}
}
public decimal N2
{
get
{
return this.__dataModel.N2;
}
set
{
if (this.__dataModel.N2 != value)
{
this.__dataModel.N2 = value;
this.OnPropertyChanged("N1");
}
}
}
public decimal Result
{
get
{
return this.__dataModel.Result;
}
set
{
if (this.__dataModel.Result != value)
{
this.__dataModel.Result = value;
this.OnPropertyChanged("N1");
}
}
}
#endregion
#region ---validation---
/// <summary>Gets an error message indicating what is wrong with this object.</summary>
public string Error
{
get { throw new NotImplementedException(); }
}
/// <summary>Gets the error message for the property with the given name.</summary>
public string this[string _columnName]
{
get
{
try
{
if (_columnName != null)
{
switch (_columnName)
{
default:
break;
}
}
return (null);
}
catch (Exception _except)
{
// mlog
Log.Exception(this.GetType().FullName, MethodBase.GetCurrentMethod().Name, _except);
return (null);
}
}
}
#endregion
}
ObservableCollection<ViewModelNumber> lstOperations;

Binding a nested object to a DataGrid in c# WPF

I am trying to bind the data displayed in a DataGrid to a dynamic list of object (WhisperModel) which is inside another object(WhisperReader). The DataGrid only displays the headers, but no values. How can I make the DataGrid dynamically update itself when the list "whispers" is changed?
Main Window XAML:
<DataGrid x:Name="whisperDataGrid" Margin="10,69,10,10" IsReadOnly="True" ItemsSource="{Binding}"/>
Main Window C#
public partial class MainWindow : Window
{
private WhisperReader wr;
public MainWindow()
{
InitializeComponent();
wr = new WhisperReader();
whisperDataGrid.DataContext = wr.whispers;
}
WhisperReader:
class WhisperReader
{
public ObservableCollection<WhisperModel> whispers { get; private set; }
public WhisperReader()
{
whispers = new ObservableCollection<WhisperModel>();
}
WhisperModel:
class WhisperModel
{
public DateTime sentTime { get; set; }
public string sender { get; set; }
public string message { get; set; }
}
I think your problem is that it doesn't know when to update itself because:
You have made the whispers list the data context.
The properties that you are binding to don't use INotifyPropertyChanged.
WhisperReader and WhisperModel are not public
All bindings must be public, must be properties, and must call the PropertyChanged method.
The PropertyChanged function triggers the binding updates.
Try this...
public partial class MainWindow : Window
{
private WhisperReader wr;
public MainWindow()
{
InitializeComponent();
wr = new WhisperReader();
whisperDataGrid.DataContext = wr;
}
public class WhisperReader : INotifyPropertyChanged
{
ObservableCollection<WhisperModel> _whispers;
public ObservableCollection<WhisperModel> whispers
{
get { return _whispers; }
private set
{
_whispers = value;
NotifyPropertyChanged();
}
}
public WhisperReader()
{
whispers = new ObservableCollection<WhisperModel>();
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class WhisperModel : INotifyPropertyChanged
{
public DateTime sentTime { get; set; }
private string _sender;
public string sender
{
get { return _sender; }
set { _sender = value; NotifyPropertyChanged();
}
private string _message;
public string message
{
get { return _message; }
set { _message = value; NotifyPropertyChanged();
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
<DataGrid x:Name="whisperDataGrid" Margin="10,69,10,10" IsReadOnly="True" AutoGenerateColumns="True" ItemsSource="{Binding whispers}"/>

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));
}
}
}

Wpf DataGrid bind selection from two Collections of same Count

I have List<Foo> F and List<Bar> B, same Count.
What is the cleanest/nicest way to bind columns F.x and B.y to same DataGrid in WPF and mantain OneWay binding from DataGrid to lists F and B.
I Would create a hybrid object:
The classes:
public class FooBar
{
public Foo F { get; set; }
public Bar B { get; set; }
}
public class Foo
{
public int X { get; set; }
}
public class Bar
{
public int Y { get; set; }
}
The view model:
public class ViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
protected void InvokePropertyChanged(string propertyName)
{
var e = new PropertyChangedEventArgs(propertyName);
if (PropertyChanged != null) PropertyChanged(this, e);
}
#endregion
public ViewModel()
{
this.FooBars.Add(new FooBar()
{
B = new Bar(){Y = 30},
F = new Foo(){ X = 100}
});
}
private ObservableCollection<FooBar> fooBars = new ObservableCollection<FooBar>();
public ObservableCollection<FooBar> FooBars
{
get { return this.fooBars; }
set
{
this.fooBars = value;
InvokePropertyChanged("FooBars");
}
}
}
The window back code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
The view:
<DataGrid ItemsSource="{Binding FooBars}" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=F.X, Mode=TwoWay}" Header="This is FOO"></DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Path=B.Y, Mode=TwoWay}" Header="This is BAR"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
Hope that helps

Categories

Resources