I have a listbox wit listboxitems with an image and a textblock.
The listbox has a custom class as datasource.
What I want is when the listboxItem is tapped. The image inside the listboxitem changes.
Here is what I have so far:
My custom class:
public class MemberUser
{
[JsonProperty("member_id", NullValueHandling = NullValueHandling.Ignore)]
public int member_id { get; private set; }
[JsonProperty("first_name", NullValueHandling = NullValueHandling.Ignore)]
public String first_name { get; private set; }
[JsonProperty("last_name", NullValueHandling = NullValueHandling.Ignore)]
public String last_name { get; private set; }
public string fullName
{
get
{
return String.Format("{0} {1}", first_name, last_name);
}
}
public bool selected{get;set;}
public string selectedImage
{
get{
if (selected)
{
return "/Assets/ic_selected.png";
}
else
{
return "/Assets/ic_not_selected.png";
}
}
}
}
My codebehind file (Only the code that you need to understand)
private OrganizationObject community;
private IEnumerable<MemberUser> memItems;
private List<MemberUser> notFoundEvents = new List<MemberUser>();
public EventAdd()
{
InitializeComponent();
BindData();
}
private async void BindData()
{
try
{
memItems = MemberDataSource.memberList;
if (memItems.Count() == 0)
{
await MemberDataSource.GetLocalMember();
memItems = MemberDataSource.memberList;
}
inviteList.DataContext = memItems;
/* foreach (MemberUser obj in memItems)
{
if (obj.accepted == 1)
{
inviteList.Items.Add(obj);
}
}*/
}
catch (KeyNotFoundException)
{
NavigationService.GoBack();
}
}
private void Selectionchanged_Eventhandler_of_Listbox(object sender, SelectionChangedEventArgs e)
{
MemberUser myobject = (sender as ListBox).SelectedItem as MemberUser;
if (myobject.selected)
{
myobject.selected = false;
}
else
{
myobject.selected = true;
}
}
My XAML
<ListBox x:Name="inviteList" ItemsSource="{Binding}" Margin="20,0,0,0" SelectionChanged="Selectionchanged_Eventhandler_of_Listbox">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Height="80" VerticalAlignment="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="0.70*" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Grid.Row="0" x:Name="img_selected" Source="{Binding selectedImage}" Width="26" Height="29"></Image>
<TextBlock Grid.Column="1" Grid.Row="0" x:Name="fullName" Text="{Binding fullName}" Foreground="#FF4C6383" FontFamily="/Membr;component/Assets/Fonts/Fonts.zip#Source Sans Pro" Height="50" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="10,0" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
So what am i missing? Also it seems like I can only tap once on each list item?
Please help !
You can achieve it with few ways:
1) Inherit you custom class from INotifyPropertyChanged
public class MemberUser : INotifyPropertyChanged
{
public int member_id { get; private set; }
public String first_name { get; private set; }
public String last_name { get; private set; }
public string fullName
{
get
{
return String.Format("{0} {1}", first_name, last_name);
}
}
public bool IsSelected
{
get
{
return _isSelected;
}
set
{
_isSelected = value;
OnPropertyChanged("selectedImage");
}
}
private bool _isSelected;
public string selectedImage
{
get
{
if (IsSelected)
{
return "/Assets/ic_selected.png";
}
else
{
return "/Assets/ic_not_selected.png";
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
2) Bind ImageSource to IsSelected property through Converter
<Image Grid.Column="0" Grid.Row="0" x:Name="img_selected" Source="{Binding IsSelected, Converter={StaticResource SelectedImageConverter}}" Width="26" Height="29"></Image>
public class SelectedImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
try
{
var isSelected = (bool)value;
return isSelected
? "/Assets/ic_selected.png"
: "/Assets/ic_not_selected.png";
}
catch (Exception)
{
return "/Assets/ic_not_selected.png";
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
If the problem isn't the images path, I think you need to implement INotifyPropertyChanged interface, in the MemberUser class, and when you set the Selected property, notify also the change of SelectedImage property. Something like:
private bool _selected;
public bool Selected
{
get{ return _selected;}
set
{
if(value!=_selected)
{
_selected=value;
OnPropertyChanged("Selected");
OnPropertyChanged("SelectedImage");
}
}
}
private void OnPropertyChanged([CallerMemberName] string propertyName="")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Try
public ImageSource selectedImage
{
ImageSource temp;
get{
if (selected)
{
temp=new BitmapImage(new Uri("ms-appx:////Assets/ic_selected.png", UriKind.RelativeOrAbsolute));
return temp ;
}
else
{
temp=new BitmapImage(new Uri("/Assets/ic_not_selected.png", UriKind.RelativeOrAbsolute));
return temp ;
}
}
}
Since Source of image Binds to an ImageSource not to string.
Check if your url requires ms-appx://// or not. (it does in windows 8.1 apps) Also You need to add an INotifyProperty Chaged extension to be able to see the property being changed instantly.
Related
I created a User Control page (SubPODetailView.xaml) that contains some entities "that entities defined from SubPO.cs" and a datagridview "that gridview is a separated class called SubInvoice.cs", and everything went good but in the datagridview, one of entities is a combobox display, the problem here is the collection list of this combobox is not displayed when running a program.
SubPO.cs
public class SubInvoice
{
public int Id { get; set; }
[Required]
public string InvoiceName { get; set; }
public DateTime Date { get; set; }
public decimal Amount { get; set; }
public List<string> Status{ get; set; }
public int SubPOId { get; set; }
public SubPO SubPO { get; set; }
}
Configuration.cs (In Migration Folder)
context.SubInvoices.AddOrUpdate(i => i.InvoiceName,
new SubInvoice
{
InvoiceName = "Invoice1",
Date = new DateTime(2020, 5, 26),
Amount = 1200,
SubPOId=context.SubPOs.First().Id,
Status = new List<string>() { "Open", "Closed", "Pending" }
});
SubPODetailViewModel.cs
<DockPanel Grid.Row="12" Margin="10">
<StackPanel DockPanel.Dock="Right" Width="86">
<Button Content="Add" Margin="10"
Command="{Binding AddInvoiceCommand}"/>
<Button Content="Remove" Margin="10"
Command="{Binding RemoveInvoiceCommand}"/>
</StackPanel>
<DataGrid ItemsSource="{Binding Invoices}"
SelectedItem="{Binding SelectedInvoice,Mode=TwoWay}"
AutoGenerateColumns="False" RowHeaderWidth="0" >
<DataGrid.Columns>
<DataGridTextColumn Header="Invoices" Width="*"
Binding="{Binding InvoiceName,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
<DataGridTemplateColumn Header="Status" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding Date,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Amount" Width="*" Binding="{Binding Amount,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
<DataGridTemplateColumn Header="Status" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Status,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</DockPanel>
SubPODetailViewModel.cs(Model)
namespace SubBV.UI.ViewModel
{
public class SubPODetailViewModel : DetailViewModelBase, ISubPODetailViewModel
{
private ISubPORepository _subPORepository;
private IMessageDialogService _messageDialogService;
private SubPOWrapper _subPO;
private SubInvoiceWrapper _selectedInvoice;
public SubPODetailViewModel(IEventAggregator eventAggregator,
IMessageDialogService messageDialogService,
ISubPORepository subPORepository) : base(eventAggregator)
{
_subPORepository = subPORepository;
_messageDialogService = messageDialogService;
AddInvoiceCommand = new DelegateCommand(OnAddInvoiceExecute);
RemoveInvoiceCommand = new DelegateCommand(OnRemoveInvoiceExecute, OnRemoveInvoiceCanExecute);
Invoices = new ObservableCollection<SubInvoiceWrapper>();
}
public SubPOWrapper SubPO
{
get { return _subPO; }
private set
{
_subPO = value;
OnPropertyChanged();
}
}
public SubInvoiceWrapper SelectedInvoice
{
get { return _selectedInvoice; }
set
{
_selectedInvoice = value;
OnPropertyChanged();
((DelegateCommand)RemoveInvoiceCommand).RaiseCanExecuteChanged();
}
}
public ICommand AddInvoiceCommand { get; }
public ICommand RemoveInvoiceCommand { get; }
public ObservableCollection<SubInvoiceWrapper> Invoices { get; }
public override async Task LoadAsync(int? subPOId)
{
var subPO = subPOId.HasValue
? await _subPORepository.GetByIdAsync(subPOId.Value)
: CreateNewSubPO();
InitializeSubInvoice(subPO.Invoices);
}
private void InitializeSubInvoice(ICollection<SubInvoice> invoices)
{
foreach (var wrapper in Invoices)
{
wrapper.PropertyChanged -= SubInvoiceWrapper_PropertyChanged;
}
Invoices.Clear();
foreach (var subInvoice in invoices)
{
var wrapper = new SubInvoiceWrapper(subInvoice);
Invoices.Add(wrapper);
wrapper.PropertyChanged += SubInvoiceWrapper_PropertyChanged;
}
}
private void SubInvoiceWrapper_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (!HasChanges)
{
HasChanges = _subPORepository.HasChanges();
}
if (e.PropertyName == nameof(SubInvoiceWrapper.HasErrors))
{
((DelegateCommand)SaveCommand).RaiseCanExecuteChanged();
}
}
private void InitializeSubPO(SubPO subPO)
{
SubPO = new SubPOWrapper(subPO);
SubPO.PropertyChanged += (s, e) =>
{
if (!HasChanges)
{
HasChanges = _subPORepository.HasChanges();
}
if (e.PropertyName == nameof(SubPO.HasErrors))
{
((DelegateCommand)SaveCommand).RaiseCanExecuteChanged();
}
};
((DelegateCommand)SaveCommand).RaiseCanExecuteChanged();
if (SubPO.Id == 0)
{
// Little trick to trigger the validation
SubPO.Title = "";
}
}
}
SubInvoiceWrapper.cs (Wraps what is contained in the DataGridView)
public class SubInvoiceWrapper:ModelWrapper<SubInvoice>
{
public SubInvoiceWrapper(SubInvoice model) : base(model)
{
}
public string InvoiceName
{
get { return GetValue<string>(); }
set { SetValue(value); }
}
public DateTime Date
{
get { return GetValue<DateTime>(); }
set { SetValue(value); }
}
public decimal Amount
{
get { return GetValue<decimal>(); }
set { SetValue(value); }
}
public List<string> Status
{
get { return GetValue<List<string>>(); }
set { SetValue(value); }
}
}
ViewModelBase.cs (contains the propertychanged)
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I created a POC in WPF MVVM architecture.In which I used combo box control you can see in the code below.
<ComboBox Name="DeptCombo" Grid.Row="4" Grid.Column="1" ItemsSource="{Binding DepartmentList,Mode=TwoWay}" SelectedItem="{Binding Path=CurrentDepartment,Mode=TwoWay}" DisplayMemberPath="DepartmentName">
</ComboBox>
here CurrentDepartment is a property of Department class.
Everything is fine, I filled that combo , saved that combo value in the database, But the only problem I was facing is, I am not able to set the saved database value in that combo.I don't get any solution regarding that.Please help me.
Add UpdateSourceTrigger
SelectedItem="{Binding Path=CurrentDepartment,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Еhe code should look something like this:
class ViewModel: INotifyPropertyChanged
{
public ObservableCollection<string> Datas { get; set; } = new ObservableCollection<string>()
{
"FF", "AA", "BB"
};
private string currentItem;
public string CurrentItem
{
get => currentItem;
set
{
currentItem = value;
OnPropertyChanged("CurrentItem");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName]string prop="")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
}
Example with Model:
PersonModel.cs:
class PersonModel
{
public string Name { get; set; }
public int Age { get; set; }
}
ViewModel.cs:
class ViewModel: INotifyPropertyChanged
{
public ObservableCollection<PersonModel> Datas { get; set; } = new ObservableCollection<PersonModel>()
{
new PersonModel(){Age = 10, Name="Tom"},
new PersonModel(){Age = 10, Name="Mark"},
};
private PersonModel currentItem;
public PersonModel CurrentItem
{
get => currentItem;
set
{
currentItem = value;
OnPropertyChanged("CurrentItem");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName]string prop="")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
}
MainWindow.xaml:
<ComboBox ItemsSource="{Binding Datas}" SelectedItem="{Binding CurrentItem, UpdateSourceTrigger=PropertyChanged}"
Height="100" Width="100">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Age}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Department Class :
public class Department : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertychanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private int id;
public int Id
{
get { return id; }
set { id = value; OnPropertychanged("Id"); }
}
private string departmentName;
public string DepartmentName
{
get { return departmentName; }
set { departmentName = value; OnPropertychanged("DepartmentName"); }
}
private bool isActive;
public bool IsActive
{
get { return isActive; }
set { isActive = value; OnPropertychanged("IsActive"); }
}
}
View Model :
private ObservableCollection<Department> departmentList;
public ObservableCollection<Department> DepartmentList
{
get { return departmentList; }
set { departmentList = value; OnPropertyChanged("DepartmentList"); }
}
private Department currentDepartment;
public Department CurrentDepartment
{
get { return currentDepartment; }
set { currentDepartment = value; OnPropertyChanged("CurrentDepartment"); }
}
private void DepartmentPop()
{
DepartmentList = new ObservableCollection<Department>
(objDepartmentService.GetAll());
}
I've read through probably 20 different posts and still can put together what I'm looking to do (maybe I'm going about it the wrong way?).
I want to make a window with a few checkboxes. These checkboxes need to have their IsChecked value bound to a member variable of an object in a List.
I'm trying something like this:
XAML
<CheckBox IsChecked="{Binding Features[0].Selected, Mode=TwoWay}">
<TextBlock Text="Feature 1" />
</CheckBox>
<CheckBox IsChecked="{Binding Features[1].Selected, Mode=TwoWay}">
<TextBlock Text="Feature 2" />
</CheckBox>
ViewModel
public static enum MyFeatureEnum
{
FEATURE1,
FEATURE2,
FEATURE3,
...
}
public class Feature : INotifyPropertyChanged
{
private bool _supported;
private bool _visible;
private bool _selected;
public MyFeatureEnum FeatureEnum { get; set; }
public bool Supported
{
get { return _supported; }
set { _supported = value; NotifyPropertyChanged("Supported"); }
}
public bool Visible
{
get { return _visible; }
set { _visible = value; NotifyPropertyChanged("Visible"); }
}
public bool Selected
{
get { return _selected; }
set { _selected = value; NotifyPropertyChanged("Selected"); }
}
public Feature(MyFeatureEnum featureEnum)
{
_supported = false;
_visible = false;
_selected = false;
FeatureEnum = featureEnum;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public List<Feature> Features { get; set; }
public InstallViewModel()
{
...
// Generate the list of features
Features = new List<Feature>();
foreach (MyFeatureEnum f in Enum.GetValues(typeof(MyFeatureEnum)))
{
Features.Add(new Feature(f));
}
...
}
This works.
What I want is to replace:
<CheckBox IsChecked="{Binding Features[0].Selected, Mode=TwoWay}">
with something like:
<CheckBox IsChecked="{Binding Features[InstallViewModel.MyFeatureEnum.FEATURE1].Selected, Mode=TwoWay}">
Is this possible?
Is this possible?
Not using a List<Feature> but you could use a Dictionary<MyFeatureEnum, Feature>:
public class InstallViewModel
{
public Dictionary<MyFeatureEnum, Feature> Features { get; set; }
public InstallViewModel()
{
// Generate the list of features
Features = new Dictionary<MyFeatureEnum, Feature>();
foreach (MyFeatureEnum f in Enum.GetValues(typeof(MyFeatureEnum)))
{
Features.Add(f, new Feature(f));
}
}
}
<CheckBox IsChecked="{Binding Features[FEATURE1].Selected, Mode=TwoWay}" />
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));
}
}
}
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));
}
}
}