StringFormat on TextBox prevents Input - c#

I need to display and set an exchange rate in a textbox.
My ViewModel contains a decimal property named ExchangeRate. By default it's only possible to enter up to two decimal places.
Exchange rates usually contains more then two decimal places so I tried to achieve this with StringFormat:
<TextBox HorizontalAlignment="Left"
IsEnabled="{Binding ExchangeRateNeeded}"
Text="{Binding ExchangeRate, UpdateSourceTrigger=PropertyChanged, Delay=250, Mode=TwoWay, StringFormat={}{0:0.00000}}"
Height="23" Margin="130,92,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="80" MaxLength="4"/>
It displays great, but I am not able to enter a value with this formatting option.
How can I enter more then 2 decimal places?
ViewModel:
public class SetupWindowViewModel : ViewModelBase
{
public ObservableCollection<SapVkorg> SapVkOrgs { get; }
public SapVkorg SelectedVkOrg { get; set; }
public ICommand OkCommand { get; }
public int Year { get; set; }
public bool ApertumNumbers { get; set; }
public ObservableCollection<Currency> Currencies { get; }
public Currency SelectedCurrency { get; set; }
public decimal ExchangeRate { get; set; }
public bool ExchangeRateNeeded { get { return SelectedCurrency != Currency.EUR; } }
public SetupWindowViewModel(Window window) : base(window)
{
OkCommand = new RelayCommand(Save, CanSave);
SapVkOrgs = mainController.GetSapVkorgs();
Currencies = mainController.GetCurrencies();
SelectedVkOrg = SettingsHolder.SapVkorg;
Year = SettingsHolder.Year;
ApertumNumbers = SettingsHolder.ApertumNumbers;
ExchangeRate = SettingsHolder.ExchangeRate;
SelectedCurrency = SettingsHolder.Currency;
}
public void Save()
{
SettingsHolder.SapVkorg = SelectedVkOrg;
SettingsHolder.Year = Year;
SettingsHolder.Currency = SelectedCurrency;
SettingsHolder.ExchangeRate = ExchangeRate;
SettingsHolder.ApertumNumbers = ApertumNumbers;
CloseWindow();
}
public bool CanSave()
{
return Year > 0 &&
SelectedVkOrg != null &&
((ExchangeRateNeeded && ExchangeRate != 1) || (!ExchangeRateNeeded));
}
}
ViewModelBase:
[AddINotifyPropertyChangedInterface]
public abstract class ViewModelBase
{
protected readonly MainController mainController;
protected static readonly log4net.ILog log = LogHelper.GetLogger();
protected readonly Window window;
public ICommand CloseWindowCommand { get; }
protected ViewModelBase(Window window)
{
mainController = MainController.GetInstance();
this.window = window;
CloseWindowCommand = new RelayCommand(CloseWindow);
Initialize();
}
protected void CloseWindow()
{
window.Close();
}
protected bool? ShowDialog(Window windowToOpen)
{
windowToOpen.Owner = window;
return windowToOpen.ShowDialog();
}
private void Initialize()
{
window.WindowStartupLocation = WindowStartupLocation.CenterOwner;
window.DataContext = this;
}
protected void DisplayAlertAndLogError(string message, Exception ex)
{
log.Error(message, ex);
MessageBox.Show(message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
}

Related

Implementing INotifyPropertyChanged in my project Xamarin

so I have a model class that I called "Objets" and I want to creat a ViewModel so that I can track the changes that happen in one of my Model class arguments which is "nbr_objet".
what should I do ?
this what I've done so far and please correct me.
Model Class :
public class Objets
{
public string Designation { get; set; }
public string Description { get; set; }
public float Prix { get; set; }
public int nbr_objet { get; set; }
public Objets(string Designation, string Description, float Prix, int nbr_objet)
{
this.Designation = Designation;
this.Description = Description;
this.Prix = Prix;
this.nbr_objet= nbr_objet;
}
}
ModelViewBase where I have the problem obviously
class ViewModelBase : INotifyPropertyChanged
{
public Objets ObjetVM { get; set; }
public int nbr_objet
{
get { return ObjetVM.nbr_objet; }
set
{
ObjetVM.nbr_objet = value;
OnPropertyChanged(nameof(ObjetVM.nbr_objet));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyname)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
}
And this is my MainPage.xaml.cs where I creat multiple "Objets"
public MenuPage()
{
InitializeComponent();
this.BindingContext = new ViewModelBase();
}
Here is running screenshot.
You can achieve it like following format in your model.
public class MyObjets : INotifyPropertyChanged
{
// public string Designation { get; set; }
// public string Description { get; set; }
// public float Prix { get; set; }
// public int nbr_objet { get; set; }
int _nbr_objet;
public int Nbr_objet
{
get
{
return _nbr_objet;
}
set
{
if (_nbr_objet != value)
{
_nbr_objet = value;
OnPropertyChanged("Nbr_objet");
}
}
}
float _prix;
public float Prix
{
get
{
return _prix;
}
set
{
if (_prix != value)
{
_prix = value;
OnPropertyChanged("Prix");
}
}
}
string _designation;
public string Designation
{
get
{
return _designation;
}
set
{
if (_designation != value)
{
_designation = value;
OnPropertyChanged("Designation");
}
}
}
string _description;
public string Description
{
get
{
return _description;
}
set
{
if (_description != value)
{
_description = value;
OnPropertyChanged("Description");
}
}
}
public MyObjets(string Designation, string Description, float Prix, int nbr_objet)
{
this._designation = Designation;
this._description = Description;
this._prix = Prix;
this._nbr_objet = nbr_objet;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then Here is Layout.
<StackLayout>
<!-- Place new controls here -->
<Label Text="{Binding Designation}"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<Label Text="{Binding Description}"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<Label Text="{Binding Prix}"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<Label Text="{Binding Nbr_objet}"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
</StackLayout>
Here is layout backend code.
public MainPage()
{
InitializeComponent();
BindingContext = new MyObjets("xxxx","cccc",1.22f,11);
}
Here is my demo about MVVM with Listview, you can refer to it as well.
https://github.com/851265601/MVVMListview
If the reply is helpful, please do not forget to mark it as answer.
======================Update========================
You want to achieve the result like following GIF?
Here is your model
public class MyObjets
{
public string Designation { get; set; }
public string Description { get; set; }
public float Prix { get; set; }
public int nbr_objet { get; set; }
public MyObjets(string Designation, string Description, float Prix, int nbr_objet)
{
this.Designation = Designation;
this.Description = Description;
this.Prix = Prix;
this.nbr_objet = nbr_objet;
}
}
Here is ViewModelBase
public class ViewModelBase: INotifyPropertyChanged
{
public ViewModelBase()
{
ObjetVM = new MyObjets("ccc","xxx",1.2f,123);
}
public MyObjets ObjetVM { get; set; }
public int nbr_objet
{
get { return ObjetVM.nbr_objet; }
set
{
ObjetVM.nbr_objet = value;
OnPropertyChanged(nameof(ObjetVM.nbr_objet));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyname)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
}
Here layout forground code.
<StackLayout>
<Label Text="{Binding nbr_objet}"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<Button Text="change the value" Clicked="Button_Clicked"></Button>
</StackLayout>
here is layout background code.
public partial class MainPage : ContentPage
{
ViewModelBase viewModelBase;
public MainPage()
{
InitializeComponent();
viewModelBase = new ViewModelBase();
BindingContext = viewModelBase;
}
private void Button_Clicked(object sender, EventArgs e)
{
viewModelBase.nbr_objet = 111;
}
}
You need to implement INotifyPropertyChanged on any object you want to track changes on. Right now you are only tracking changes on the viewmodel, so you also need to add INotifyPropertyChanged on the Objets class, too, as well as each property within the class with the getters/setters with OnPropertyChanged like you did in the viewmodel.
public class Objets: INotifyPropertyChanged
When you change the property "nbr_objet" you raise that your property inside the ObjetVM has changed, but this is not your bindingContext - your bindingContext is the ViewModelBase.
So rather rewrite it:
private int nbr_object;
public int Nbr_objet_property
{
get { return nbr_objet; }
set
{
nbr_objet = value;
OnPropertyChanged(nameof(Nbr_objet_property));
}
}
And then everytime you cahnge "Nbr_objet_property" it should update whatever you binded it to.
Also, "ObjetVM" is no a viewModel since it does not implement the INotifyPropertyChanged logic.
Hope it makes sense? :)

Passing parameters from Detail to MainViewModel in MVVMCross

I have two views : MainView and DetailView. I have a list of items to display and when user select an item and I am passing item properties to DetailViewModel and user could able to update these values.
Everything works so far, but I wonder how am I passing back to updated values to the MainViewModel ?
MainViewModel.cs
public MainViewModel SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
ShowViewModel<DetailViewModel>(
new DetailViewModel.Parameter
{
Date = Date,
Age = _selectedItem.Age,
Category = _selectedItem.Category,
Discount = _selectedItem.Discount,
}
);
RaisePropertyChanged(() => SelectedItem);
}
}
DetailViewModel.cs
public class DetailViewModel: MvxViewModel
{
public double Age { get; set; }
public double Category { get; set; }
public double Discount { get; set; }
public class Parameter
{
public DateTime Date { get; set; }
public double Age { get; set; }
public int Category{ get; set; }
public double Discount { get; set; }
}
public void Init(Parameter param)
{
Age = param.Age;
Category = param.Category;
Discount = param.Discount ;
}
}
One way to pass variables between ViewModels is a Messenger based solution.
MvvmCross Messenger can be found in NuGet.
MainViewModel
private readonly IMvxMessenger _messenger;
private readonly MvxSubscriptionToken _token;
public MainViewModel(IMvxMessenger messenger) {
_messenger = messenger;
_token = messenger.Subscribe<SelectedItemMessage>(OnMessageReceived);;
}
private void OnMessageReceived(SelectedItemMessage obj)
{
SelectedItem = obj.SelectedItem;
}
DetailViewModel
private readonly IMvxMessenger _messenger;
public DetailViewModel(IMvxMessenger messenger) {
_messenger = messenger;
}
public void YourUpdateMethod() {
var message = new SelectedItemMessage(this, SelectedItem); //SelectedItem assumed it is a ViewModel property.
_messenger.Publish(message, typeof(SelectedItemMessage));
}
SelectedItemMessage
public class SelectedItemMessage : MvxMessage
{
public SelectedItemMessage(object sender, SelectedItem selectedItem) : base(sender)
{
SelectedItem = selectedItem;
}
public SelectedItem SelectedItem { get; set; }
}
Take a look at http://slodge.blogspot.nl/2013/05/n9-getting-message-n1-days-of-mvvmcross.html for a full guide to MvvmCross Messenges.
Edit using age and category in Message
public SelectedItemMessage(object sender, double age, int category) : base(sender)
{
Age = age;
Category = category;
}
public double Age { get; set; }
public int Category{ get; set; }
}
Changing the MainViewModel OnMessageReceived method
private void OnMessageReceived(SelectedItemMessage obj)
{
Age = obj.Age;
Category= obj.Category;
}
Why not just keep a reference to DetailViewModel when you create it in MainViewModel? Then any values changed in the DetailViewModel instance will be available via that reference in MainViewModel.
private DetailViewModel _detailVM;
public MainViewModel SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
_detailVM = new DetailViewModel.Parameter {
Date = Date,
Age = _selectedItem.Age,
Category = _selectedItem.Category,
Discount = _selectedItem.Discount
};
ShowViewModel<DetailViewModel>(_detailVM);
RaisePropertyChanged(() => SelectedItem);
}
}

Bound Textbox Not Updating on Change in Codebehind

I copied this code from another project and can't figure out why it isn't working. My observable collections are working great binding and updating, but my textboxes aren't changing. I have a button click that lets the user pick a directory (DirectoryBrowse() method) and then assigns that value to the data context's property that is bound to the textbox. PropertyChanged is always null and I can't figure out why! The initial binding works just fine, just note when I change the value in the code-behind. I've been at this entirely too long, but any help would be appreciated!
DataContext class:
[Serializable]
public class Settings : ViewModels.ViewModelEntity
{
public static Settings defaultSettings { get; set; }
private string _ExportDir;
public string ExportDir
{
get { return this._ExportDir; }
set
{
if (this._ExportDir != value)
{
this._ExportDir = value;
this.NotifyPropertyChanged("ExportDir");
}
}
}
private string _LastRunTime;
public string LastRunTime
{
get { return this._LastRunTime; }
set
{
if (this._LastRunTime != value)
{
this._LastRunTime = value;
this.NotifyPropertyChanged("LastRunTime");
}
}
}
private string _TSCertPath;
public string TSCertPath
{
get { return this._TSCertPath; }
set
{
if (this._TSCertPath != value)
{
this._TSCertPath = value;
this.NotifyPropertyChanged("TSCertPath");
}
}
}
public ObservableCollection<Map> Brokers { get; set; }
public ObservableCollection<Account> Accounts { get; set; }
public List<Holiday> Holidays { get; set; }
public bool RefreshHolidays { get; set; }
public string ProxyServer { get; set; }
public string ProxyPort { get; set; }
public string ProxyUsername { get; set; }
public string ProxyPassword { get; set; }
public bool TSProd { get; set; }
public string TSTriad { get; set; }
public string TSPassword { get; set; }
public string TSCertPassword { get; set; }
public Settings()
{
this.Brokers = new ObservableCollection<Map>();
this.Accounts = new ObservableCollection<Account>();
}
}
Xaml:
<TextBlock TextWrapping="Wrap" Text="File Export Path*"/>
<TextBox TextWrapping="Wrap" Text="{Binding Path=ExportDir, Mode=TwoWay}" />
<Button x:Name="btnBrowseExportDir" Content="..." Click="btnBrowseExportDir_Click"/>
Code-behind:
public MainWindow()
{
InitializeComponent();
Settings.Initialize();
this.DataContext = Settings.defaultSettings;
string[] args = Environment.GetCommandLineArgs();
if (args.Contains("create"))
{
this.Close();
}
}
private string DirectoryBrowse()
{
CommonOpenFileDialog dialog = new CommonOpenFileDialog();
dialog.IsFolderPicker = true;
CommonFileDialogResult result = dialog.ShowDialog();
if (result.ToString().ToUpper() == "OK")
{
if (!Directory.Exists(dialog.FileNames.First()))
{
this.lblStatus.Text = "Invalid directory selected";
return string.Empty;
}
else
{
return dialog.FileNames.First();
}
}
else
{
this.lblStatus.Text = "Invalid directory selected";
return string.Empty;
}
}
private void btnBrowseExportDir_Click(object sender, RoutedEventArgs e)
{
Settings.defaultSettings.ExportDir = DirectoryBrowse();
}
ViewModelEntity:
public class ViewModelEntity
{
public event PropertyChangedEventHandler PropertyChanged;
public virtual void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Settings.defaultSettings is never assigned a value. So the databinding have nothing to work with.
Thoug code for Settings.Initialize() is missing.
#Dave and #Icepickle showed me what I was missing, no implementaiton of INotifyPropertyChanged!

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 do I bind the foreground color of a label in a datagrid?

I have a wpf datagrid with a label in a datatemplate. I want to bind the color of the text to a property and it's not working.
Here is the xaml.
<DataGrid x:Name="ResultsDataGrid" CanUserSortColumns="True" MouseDown="ResultsDataGrid_OnMouseDown" SelectionChanged="ResultsDataGrid_OnSelectionChanged"
IsReadOnly="False" AlternatingRowBackground="WhiteSmoke" CanUserAddRows="False" Margin="10" AutoGenerateColumns="False" VerticalAlignment="Stretch">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Test ID" Width="150" IsReadOnly="True" SortMemberPath="TestDate">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Label Foreground="{Binding PassedColor}" Content="{Binding TestID}"></Label>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
And here is the property.
public Brush PassedColor
{
get
{
return new SolidBrush(Color.Red);
}
}
I can't seem to figure out what I'm doing wrong.
If it remove the binding and set the foreground to red it works. So it's definitely something with the binding.
Edit:
Here is the entire object
public class LabelInfo : INotifyPropertyChanged
{
private bool _isSelected;
private double? _karat;
private bool _passed;
public string TestID { get; set; }
public string Label1 { get; set; }
public string Label2 { get; set; }
public string Value1 { get; set; }
public string Value2 { get; set; }
public string HasPassed { get { return Passed ? "Yes" : "No"; } }
public Brush PassColor
{
get
{
return Brushes.Red;
}
}
public bool Passed
{
get { return _passed; }
set
{
_passed = value;
NotifyPropertyChanged();
}
}
public bool Final { get; set; }
public DateTime? TestDate { get; set; }
public Double RealTime { get; set; }
public string JTVID { get; set; }
public int AnalysisID { get; set; }
public List<ElementResults> Elements { get; set; }
public double Karat
{
get
{
if (_karat == null)
_karat = CalculateKarat();
return _karat.Value;
}
set { _karat = value; }
}
public bool PlatingAlert
{
get
{
return Karat < 7.5;
}
}
public bool IsSelected
{
get { return _isSelected; }
set { _isSelected = value; NotifyPropertyChanged(); }
}
public bool PotentialCoating { get; set; }
private double CalculateKarat()
{
if (Elements == null || Elements.Count == 0) return 0;
return Elements.Where(ex => ex.Name.ToLower().Trim() == "au").Select(ex => ex.Level).FirstOrDefault();
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
You are using System.Drawing.Brush, which is from WinForms, but you should use the WPF System.Windows.Media.Brush. Change your code to this:
using System.Windows.Media;
public Brush PassedColor
{
get { return new SolidColorBrush(Colors.Red); }
}
or
public Brush PassedColor
{
get { return Brushes.Red; }
}
Perhaps remove System.Drawing from the referenced assemblies. Then you'll immediately find all those flaws.

Categories

Resources