WPF TextBlock binding to a ViewModel using TextBlock extension not working - wpf

I am binding a view model to a TextBlock. The Inlines property is not a DependencyProperty so I made a TextBlockExtensions class and created an attached DependencyProperty called BindableInlines.
Here below is my View Model.
public class MainWindowModel: INotifyPropertyChanged
{
private buttonAddNewTextCommand _btnAddNewTextCommand;
public ObservableCollection<Inline> ProcessTrackerInlines { get; set; }
public ICommand btnAddNewTextCommand
{
get
{
return _btnAddNewTextCommand;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public MainWindowModel()
{
_btnAddNewTextCommand = new buttonAddNewTextCommand(this);
loadProcessTracker();
}
public void addErrorLine(String errorMessage)
{
addText(errorMessage, Brushes.Red, true);
}
public void addNewTextLine()
{
String message = Guid.NewGuid().ToString();
var rand = new Random();
byte[] brushes = new byte[4];
rand.NextBytes(brushes);
ProcessTrackerInlines.Add(new LineBreak());
ProcessTrackerInlines.Add(addText(message, new SolidColorBrush(Color.FromArgb(brushes[0], brushes[1], brushes[2], brushes[3])), true));
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("ProcessTrackerInlines"));
}
}
private void loadProcessTracker()
{
ProcessTrackerInlines = new ObservableCollection<Inline>();
var rand = new Random();
byte[] brushes = new byte[4];
for(int i=0; i<5; i++)
{
rand.NextBytes(brushes);
ProcessTrackerInlines.Add(addText($"{Guid.NewGuid().ToString()}\r\n", new SolidColorBrush(Color.FromArgb(brushes[0], brushes[1], brushes[2], brushes[3])), true));
}
}
private Run addText(String textToAdd, Brush foreground = null, Boolean? addTime = null)
{
if (addTime ?? false)
{
textToAdd = addCurrentTime(textToAdd);
}
if (foreground != null)
{
return new Run(textToAdd) { Foreground = foreground };
}
else
{
return new Run(textToAdd);
}
}
private String addCurrentTime(String prefixText)
{
return $"[{DateTime.Now.ToString("h:mm ss tt")}] {prefixText}";
}
}
public class buttonAddNewTextCommand : ICommand
{
private MainWindowModel owner;
public buttonAddNewTextCommand(MainWindowModel _object)
{
owner = _object;
}
public Boolean CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
owner.addNewTextLine();
}
public event EventHandler CanExecuteChanged;
}
My TextBlockExtensions class:
public class TextBlockExtensions
{
public static IEnumerable<Inline> GetBindableInlines(DependencyObject obj)
{
return (IEnumerable<Inline>)obj.GetValue(BindableInlinesProperty);
}
public static void SetBindableInlines(DependencyObject obj, IEnumerable<Inline> value)
{
obj.SetValue(BindableInlinesProperty, value);
}
public static readonly DependencyProperty BindableInlinesProperty =
DependencyProperty.RegisterAttached("BindableInlines", typeof(IEnumerable<Inline>), typeof(TextBlockExtensions), new PropertyMetadata(null, OnBindableInlinesChanged));
private static void OnBindableInlinesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var Target = d as TextBlock;
if (Target != null)
{
Target.Inlines.Clear();
Target.Inlines.AddRange((System.Collections.IEnumerable)e.NewValue);
}
}
}
And my XAML:
<Window x:Class="MVVMTextBlock.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MVVMTextBlock"
xmlns:viewModels="clr-namespace:MVVMTextBlock.ViewModels"
xmlns:extensions="clr-namespace:MVVMTextBlock"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<viewModels:MainWindowModel/>
</Window.DataContext>
<Grid>
<TextBlock extensions:TextBlockExtensions.BindableInlines="{Binding ProcessTrackerInlines, Mode=OneWay}" HorizontalAlignment="Left" TextWrapping="Wrap" VerticalAlignment="Top" Width="578" Margin="25,0,0,0" Height="398"/>
<Button Content="Add New Text" HorizontalAlignment="Left" VerticalAlignment="Top" Width="123" Margin="638,10,0,0" Command="{Binding btnAddNewTextCommand, Mode=OneWay}"/>
</Grid>
</Window>
When the button is clicked the MainWindowModel.addNewTextLine() method is getting hit but the TextBlockExtensions.OnBindableInlinesChanged() is not. So the TextBlock is not getting updated.
I have already changed the BindableInlinesProperty to be an ObservableCollection but it didn't work.
What I am doing wrong?

Related

Datagrid don't show elements MVVM

I am trying to load a list in my datagrid when pressing the button but it does not show anything.
Do you know where the error is?
Thanks a lot
<UserControl x:Class="HiberusHubClient.View.RatesView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:HiberusHubClient.View"
mc:Ignorable="d"
x:Name="RatesViewControl"
d:DesignHeight="450" d:DesignWidth="800"
DataContext="RatesViewControlDataContext">
<Grid>
<StackPanel HorizontalAlignment = "Left">
<Button x:Name="btnUpdate" Width="100" Height="20" HorizontalAlignment="Center" Grid.Row="1" Content="Update" Command="{Binding Path=UpdateCommand}" />
<StackPanel Orientation = "Horizontal">
<DataGrid Width="200" Height="400" AutoGenerateColumns="True" ItemsSource="{Binding LstConvertRates,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Name="lstDataGrid" />
</StackPanel>
</StackPanel>
</Grid>
</UserControl>
public partial class RatesView : UserControl
{
public RatesView()
{
InitializeComponent();
DataContext = new ConvertRatesViewModel();
}
}
ConvertRate.cs
public class ConvertRate
{
}
public class ConvertRates : INotifyPropertyChanged
{
private string currencyFrom;
private string currencyTo;
private string rate;
public string CurrencyFrom
{
get
{
return currencyFrom;
}
set
{
if (currencyFrom != value)
{
currencyFrom = value;
RaisePropertyChanged("CurrencyFrom");
}
}
}
public string CurrencyTo
{
get { return currencyTo; }
set
{
if (currencyTo != value)
{
currencyTo = value;
RaisePropertyChanged("CurrencyTo");
}
}
}
public string Rate
{
get { return rate; }
set
{
if (rate != value)
{
rate = value;
RaisePropertyChanged("Rate");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
ConvertRatesViewModel .cs
internal class ConvertRatesViewModel : INotifyPropertyChanged
{
private ObservableCollection<ConvertRates> lstConvertRates;
public ObservableCollection<ConvertRates> LstConvertRates
{
get
{
return lstConvertRates;
}
set
{
lstConvertRates = value;
//if u want Observable Collection to get updated on edit either
RaisePropertyChanged("LstConvertRates");
}
}
public void getConvertRates()
{
HiberusHubeReference.RequestHubeRestService hiberusHubeReference = new HiberusHubeReference.RequestHubeRestService();
JavaScriptSerializer json = new JavaScriptSerializer();
string aa = json.Serialize(hiberusHubeReference.RequestRates());
ObservableCollection<ConvertRates> oMycustomclassname = Newtonsoft.Json.JsonConvert.DeserializeObject<ObservableCollection<ConvertRates>>(aa);
LstConvertRates = oMycustomclassname;
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
private ICommand mUpdater;
public ICommand UpdateCommand
{
get
{
if (mUpdater == null)
mUpdater = new Updater();
return mUpdater;
}
set
{
mUpdater = value;
}
}
}
internal class Updater : ICommand
{
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
ConvertRatesViewModel convertRatesViewModel = new ConvertRatesViewModel();
convertRatesViewModel.getConvertRates();
}
}
If you set DataContext in code behind then there is no need to set DataContext also in xaml. You can remove DataContext="RatesViewControlDataContext" from RatesView.xaml. Btw. DataContext="RatesViewControlDataContext" means DataContext property is set to string "RatesViewControlDataContext". You probably don't this.
Try to use existing implementation of ICommand interface like RelayCommand.
public class RelayCommand : ICommand
{
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
public RelayCommand(Predicate<object> canExecute, Action<object> execute)
{
_canExecute = canExecute;
_execute = execute;
}
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public bool CanExecute(object parameter)
{
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
For your UpdateCommand you can modify the setter and create a new instance of RelayCommand. In the constructor you can specify whether the command should be executed and which method on your ConvertRatesViewModel will be called when you press btnUpdate button.
private ICommand mUpdater;
public ICommand UpdateCommand
{
get
{
if (mUpdater == null)
mUpdater = new RelayCommand(x => true, x => getConvertRates());
return mUpdater;
}
set
{
mUpdater = value;
}
}

"PropertyChange" is not firing up when text is changed or inserted in Textbox present inside my view

I have two TextBoxes inside my View on which I am trying to implement a simple validation using MVVM design pattern.The issue is even when my ViewModel is implementing Inotification Changed interface and the property is bound tot the text property of the TextBox,on entering text propertyChange event never fires.I don't know where I have gone wrong.Please help.Its been bugging me for quite a while.
ViewModel :
class TextBoxValidationViewModel : ViewModelBase, IDataErrorInfo
{
private readonly TextBoxValidationModel _textbxValModel;
private Dictionary<string, bool> validProperties;
private bool allPropertiesValid = false;
private DelegateCommand exitCommand;
private DelegateCommand saveCommand;
public TextBoxValidationViewModel(TextBoxValidationModel newTextBoxValObj)
{
this._textbxValModel = newTextBoxValObj;
this.validProperties = new Dictionary<string, bool>();
this.validProperties.Add("BuyTh", false);
this.validProperties.Add("SellTh", false);
}
public string BuyTh
{
get { return _textbxValModel.BuyTh; }
set
{
if (_textbxValModel.BuyTh != value)
{
_textbxValModel.BuyTh = value;
base.OnPropertyChanged("BuyTh");
}
}
}
public string SellTh
{
get { return _textbxValModel.SellTh; }
set
{
if (_textbxValModel.SellTh != value)
{
_textbxValModel.SellTh = value;
base.OnPropertyChanged("SellTh");
}
}
}
public bool AllPropertiesValid
{
get { return allPropertiesValid; }
set
{
if (allPropertiesValid != value)
{
allPropertiesValid = value;
base.OnPropertyChanged("AllPropertiesValid");
}
}
}
public string this[string propertyName]
{
get
{
string error = (_textbxValModel as IDataErrorInfo)[propertyName];
validProperties[propertyName] = String.IsNullOrEmpty(error) ? true : false;
ValidateProperties();
CommandManager.InvalidateRequerySuggested();
return error;
}
}
public string Error
{
get
{
return (_textbxValModel as IDataErrorInfo).Error;
}
}
public ICommand ExitCommand
{
get
{
if (exitCommand == null)
{
exitCommand = new DelegateCommand(Exit);
}
return exitCommand;
}
}
public ICommand SaveCommand
{
get
{
if (saveCommand == null)
{
saveCommand = new DelegateCommand(Save);
}
return saveCommand;
}
}
#region private helpers
private void ValidateProperties()
{
foreach (bool isValid in validProperties.Values)
{
if (!isValid)
{
this.AllPropertiesValid = false;
return;
}
}
this.AllPropertiesValid = true;
}
private void Exit()
{
Application.Current.Shutdown();
}
private void Save()
{
_textbxValModel.Save();
}
}
}
#endregion
Model :
class TextBoxValidationModel : IDataErrorInfo
{
public string BuyTh { get; set; }
public string SellTh { get; set; }
public void Save()
{
//Insert code to save new Product to database etc
}
public string this[string propertyName]
{
get
{
string validationResult = null;
switch (propertyName)
{
case "BuyTh":
validationResult = ValidateName();
break;
case "SellTh":
validationResult = ValidateName();
break;
default:
throw new ApplicationException("Unknown Property being validated on Product.");
}
return validationResult;
}
}
public string Error
{
get
{
throw new NotImplementedException();
}
}
private string ValidateName()
{
return "Entered in validation Function";
}
}
}
ViewModelBase abstract Class :
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Application Start event code:
private void Application_Startup(object sender, StartupEventArgs e)
{
textboxvalwpf.Model.TextBoxValidationModel newTextBoxValObj = new Model.TextBoxValidationModel();
TextBoxValidation _txtBoxValView = new TextBoxValidation();
_txtBoxValView.DataContext = new textboxvalwpf.ViewModel.TextBoxValidationViewModel(newTextBoxValObj);
// _txtBoxValView.Show();
}
}
View Xaml code:
<Window x:Class="textboxvalwpf.TextBoxValidation"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:c="clr-namespace:textboxvalwpf.Commands"
xmlns:local="clr-namespace:textboxvalwpf"
mc:Ignorable="d"
Title="TextBoxValidation" Height="300" Width="300">
<Grid>
<TextBox x:Name="textBox" HorizontalAlignment="Left" Height="23" Margin="86,44,0,0" TextWrapping="Wrap" Text="{Binding Path=BuyTh, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="120"/>
<TextBox x:Name="textBox1" HorizontalAlignment="Left" Height="23" Margin="88,121,0,0" TextWrapping="Wrap" Text="{Binding Path=SellTh,ValidatesOnDataErrors=True,UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="120"/>
<Label x:Name="label_BuyTh" Content="Buy Th" HorizontalAlignment="Left" Margin="10,44,0,0" VerticalAlignment="Top" Width="71"/>
<Label x:Name="label_SellTh" Content="Sell Th" HorizontalAlignment="Left" Margin="10,117,0,0" VerticalAlignment="Top" Width="71"/>
</Grid>
</Window>
In App.xaml, you'll find a StartupUri attribute. By default, it looks like this:
<Application x:Class="WPFTest.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFTest"
StartupUri="MainWindow.xaml">
If you renamed MainWindow.xaml to TextBoxValidation.xaml, as I think you did, it'll look like this:
StartupUri="TextBoxValidation.xaml">
Your application will automatically create an instance of that StartupUri window and show it. That'll be the application's main window, so the application will shut down when it closes.
That instance won't have a viewmodel, because nothing in your code gives it one. You are creating your own instance of the window, giving it a viewmodel, and then doing nothing with it because another instance is being shown. I imagine you thought just creating the window was enough to show it, but that's not what's happening at all. Before you commented out the Show() call, you did have one window with a working viewmodel that did the validation and updated the viewmodel properties.
Let the App create the window the way it wants to.
A quick, simple fix is to delete that Startup event handler from App and move your viewmodel creation code into TextBoxValidation's constructor:
public TextBoxValidation()
{
InitializeComponent();
textboxvalwpf.Model.TextBoxValidationModel newTextBoxValObj = new Model.TextBoxValidationModel();
this.DataContext = new textboxvalwpf.ViewModel.TextBoxValidationViewModel(newTextBoxValObj);
}

SelectedItem on ComboBox

There is a ComboBox in the application which is bound to a collection of items. There are cases that user can select an item from the ComboBox but the selected item might not be ready yet so the ComboBox selected item must get back to the previous selected item (or some other item in the collection), but in the current application ComboBox always shows the selected item from the user instead of retrieving the valid item after setting it back and calling notify property change.
The flowing is a simplified code of which shows the problem.
public partial class MainWindow : Window, INotifyPropertyChanged
{
private List<Customer> _Customers = new List<Customer>();
public List<string> CustomerNames
{
get
{
var list = new List<string>();
foreach (var c in _Customers)
{
list.Add(c.Name);
}
return list; ;
}
}
public string CustomerName
{
get
{
var customer = _Customers.Where(c => c.IsReady).FirstOrDefault();
return customer.Name;
}
set
{
NotifyPropertyChanged("CustomerName");
}
}
public MainWindow()
{
SetupCustomers();
InitializeComponent();
this.DataContext = this;
}
private void SetupCustomers()
{
_Customers.Add(new Customer("c1", true));
_Customers.Add(new Customer("c2", false));
_Customers.Add(new Customer("c3", false));
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Customer
{
public Customer(string name, bool isReady)
{
this.Name = name;
this.IsReady = isReady;
}
public bool IsReady { get; set; }
public string Name { get; set; }
public override bool Equals(object obj)
{
return base.Equals(obj);
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}
<Window x:Class="TryComboboxReset.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ComboBox Width="100"
Height="25"
ItemsSource="{Binding Path=CustomerNames, Mode=OneWay}"
SelectedItem="{Binding Path=CustomerName, Mode=TwoWay}"/>
</Grid>
The problem was UI thread, I used dispatcher to fix this problem
public partial class MainWindow : Window, INotifyPropertyChanged
{
private ObservableCollection<Customer> _Customers =
new ObservableCollection<Customer>();
public ObservableCollection<Customer> CustomerNames
{
get
{
return _Customers;
}
}
public Customer CustomerName
{
get
{
return _Customers.Where(c => c.IsReady == true).FirstOrDefault();
}
set
{
// Delay the revert
Application.Current.Dispatcher.BeginInvoke(
new Action(() => NotifyPropertyChanged("CustomerName")), DispatcherPriority.ContextIdle, null);
}
}
public MainWindow()
{
SetupCustomers();
InitializeComponent();
this.DataContext = this;
}
private void SetupCustomers()
{
_Customers.Add(new Customer("c1", true));
_Customers.Add(new Customer("c2", false));
_Customers.Add(new Customer("c3", false));
CustomerName = _Customers.Where(c => c.IsReady == true).FirstOrDefault();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Customer
{
public Customer(string name, bool isReady)
{
this.Name = name;
this.IsReady = isReady;
}
public bool IsReady { get; set; }
public string Name { get; set; }
}
<ComboBox Width="400"
Height="25"
ItemsSource="{Binding Path=CustomerNames}"
SelectedValue="{Binding CustomerName,Mode=TwoWay}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
You aren't actually setting the value of the selected customer name in your setter, and your getter is always going to return the first "ready" customer name it finds...
If I'm understanding the problem correctly, you need to be doing something more along these lines:
private string _customerName = null;
public string CustomerName
{
get
{
if(_customerName == null)
{
_customerName = _Customers.Where(c => c.IsReady).FirstOrDefault().Name;
}
return _customerName;
}
set
{
_customerName = value;
NotifyPropertyChanged("CustomerName");
}
}

WPF Binding problem

I have a textbox which I need to bind a string to.
<TextBox Name="txtDoc" Margin="5" Text ="{Binding Source={x:Static local:DocumentViewModel.FileText}, Path=FileText}">
The FileText property is changed on a different class:
DocumentViewModel.GetInstance().FileText = File.ReadAllText(document.Path);
The DocumentViewModel is a class with Singleton:
public class DocumentViewModel : INotifyPropertyChanged
{
private static string fileText;
public string FileText
{
get { return fileText; }
set
{
fileText = value; // Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("FileText");
}
}
private void OnPropertyChanged(string filetext)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(filetext));
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private static DocumentViewModel instance = new DocumentViewModel();
private DocumentViewModel() { }
public static DocumentViewModel GetInstance()
{
return instance;
}
}
I need to be able to change the value of the FileText property and reflect this change in the textbox.
It's not working.
I tried using TextBox as a static property but then the Onp
Try to set the source to your viewmodel instead of the property itself, and set the instance property to public? {Binding Source={x:Static local:DocumentViewModel.instance}, Path=FileText}
Edit: Included a complete example, that working for me:
Xaml:
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Test"
Title="MainWindow" Height="350" Width="525"
Loaded="Window_Loaded">
<TextBox Name="txtDoc" Margin="5"
Text="{Binding Source={x:Static local:DocumentViewModel.Instance}, Path=FileText}" />
</Window>
Code-behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
DocumentViewModel.Instance.FileText = "Hello world!";
}
}
public class DocumentViewModel : INotifyPropertyChanged
{
#region Singleton implementation
// Static constructor to create the singleton instance.
static DocumentViewModel()
{
DocumentViewModel.Instance = new DocumentViewModel();
}
public static DocumentViewModel Instance { get; private set; }
#endregion
private static string fileText;
public string FileText
{
get { return fileText; }
set
{
if (fileText != value)
{
fileText = value;
OnPropertyChanged("FileText");
}
}
}
#region INotifyPropertyChanged
private void OnPropertyChanged(string filetext)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(filetext));
}
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}

Silverlight IDataErrorInfo message does not show in custom control's textbox

I created a custom password box user control which is able to show and hide the password. It just swaps out the standard password box with a textbox which is bound to the same password string property. It all works fine but now my data validation errors are no more shown, although they are being generated correctly in the background. Here's the xaml from my user control:
<UserControl x:Class="Controls.EAPPasswordBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400" x:Name="_root">
<Grid x:Name="LayoutRoot" Background="White">
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Top">
<PasswordBox x:Name="pwdBox" Password="{Binding Password, Mode=TwoWay,ValidatesOnDataErrors=True}" />
<TextBox x:Name="txtBox" Text="{Binding Password, Mode=TwoWay,ValidatesOnDataErrors=True}" />
</StackPanel>
</Grid>
Here's how I use it in a view:
<local:EAPPasswordBox x:Name="pwdBox"
Grid.Column="1" Grid.Row="0" Grid.ColumnSpan="2" Password="{Binding password,Mode=TwoWay, ValidatesOnDataErrors=True}" ShowText="{Binding showPassword,Mode=TwoWay}"></local:EAPPasswordBox>
in the Parent view's viewmodel we implemented IDataErrorInfo like this:
public string this[string columnName]
{
get
{
string Result = "";
switch(columnName.ToLower())
{
case "password":
{
Result = Validatepassword();
break;
}
case "password2":
{
Result = Validatepassword2();
break;
}
default:
{
Result = this.ValidateStringValue(columnName);
break;
}
}
return Result;
}
}
Now when I enter text in the custom password box, the validation logic is called just fine but it's not displayed. Do I have to adjust my user control for this?
EDIT: Here's the code behind of my passwordbox:
public partial class EAPPasswordBox : UserControl, INotifyPropertyChanged
{
public bool ShowText
{
get { return (bool)GetValue(ShowTextProperty); }
set {
SetValue(ShowTextProperty, value);
if (value == true)
{
this.pwdBox.Visibility = System.Windows.Visibility.Collapsed;
this.txtBox.Visibility = System.Windows.Visibility.Visible;
}
else
{
this.pwdBox.Visibility = System.Windows.Visibility.Visible;
this.txtBox.Visibility = System.Windows.Visibility.Collapsed;
}
}
}
public string Password
{
get { return (string)GetValue(PasswordProperty); }
set { SetValue(PasswordProperty, value); }
}
private Visibility _PwdBoxVisibility;
public Visibility PwdBoxVisibility
{
get { return _PwdBoxVisibility; }
set
{
_PwdBoxVisibility = value; NotifyPropertyChanged("PwdBoxVisibility");
}
}
private Visibility _TxtBoxVisibility;
public Visibility TxtBoxVisibility
{
get { return _TxtBoxVisibility; }
set
{
_TxtBoxVisibility = value; NotifyPropertyChanged("TxtBoxVisibility");
}
}
public static readonly DependencyProperty PasswordProperty =
DependencyProperty.Register("Password", typeof(string), typeof(EAPPasswordBox), null);
public static readonly DependencyProperty ShowTextProperty =
DependencyProperty.Register("ShowText", typeof(bool), typeof(EAPPasswordBox), new PropertyMetadata(OnShowTextPropertyChanged));
public EAPPasswordBox()
{
InitializeComponent();
this.pwdBox.SetBinding(PasswordBox.PasswordProperty, new System.Windows.Data.Binding() { Source = this, Path = new PropertyPath("Password"), Mode = BindingMode.TwoWay,ValidatesOnDataErrors=true });
this.txtBox.SetBinding(TextBox.TextProperty, new System.Windows.Data.Binding() { Source = this, Path = new PropertyPath("Password"), Mode = BindingMode.TwoWay, ValidatesOnDataErrors=true });
this.ShowText = false;
}
private static void OnShowTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
EAPPasswordBox passwordBox = d as EAPPasswordBox;
if (passwordBox != null)
{
passwordBox.ShowText=(bool)e.NewValue;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
2nd Edit: It would also help if someone would just explain to me the basics of binding properties of usercontrols in the xaml of a parent window/control. I dont quite understand why the usercontrol doesnt get the property changed events of the corresponding parent views viewmodel properties since it is bound to those via xaml.
Here's my solution at last. Since I realized that the DataContext of the user control automatically is the ViewModel of the parent view, I dumped the binding of the Password dependency property completely. I introduced a new parameter in the control which has to be set to the password property of the parent view model. I then use this string to do a manual binding of the textbox and the password box in the loaded event of the control. Here's my code:
public partial class EAPPasswordBox : UserControl, INotifyPropertyChanged
{
public bool ShowText
{
get { return (bool)GetValue(ShowTextProperty); }
set {
SetValue(ShowTextProperty, value);
if (value == true)
{
this.pwdBox.Visibility = System.Windows.Visibility.Collapsed;
this.txtBox.Visibility = System.Windows.Visibility.Visible;
}
else
{
this.pwdBox.Visibility = System.Windows.Visibility.Visible;
this.txtBox.Visibility = System.Windows.Visibility.Collapsed;
}
}
}
public string PasswordPropertyName { get; set; }
private Visibility _PwdBoxVisibility;
public Visibility PwdBoxVisibility
{
get { return _PwdBoxVisibility; }
set
{
_PwdBoxVisibility = value; NotifyPropertyChanged("PwdBoxVisibility");
}
}
private Visibility _TxtBoxVisibility;
public Visibility TxtBoxVisibility
{
get { return _TxtBoxVisibility; }
set
{
_TxtBoxVisibility = value; NotifyPropertyChanged("TxtBoxVisibility");
}
}
public static readonly DependencyProperty ShowTextProperty =
DependencyProperty.Register("ShowText", typeof(bool), typeof(EAPPasswordBox), new PropertyMetadata(OnShowTextPropertyChanged));
public EAPPasswordBox()
{
InitializeComponent();
this.ShowText = false;
}
private static void OnShowTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
EAPPasswordBox passwordBox = d as EAPPasswordBox;
if (passwordBox != null)
{
passwordBox.ShowText=(bool)e.NewValue;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
private void _root_Loaded(object sender, RoutedEventArgs e)
{
this.pwdBox.SetBinding(PasswordBox.PasswordProperty, new System.Windows.Data.Binding() { Source = this.DataContext, Path = new PropertyPath(PasswordPropertyName), Mode = BindingMode.TwoWay, ValidatesOnDataErrors = true });
this.txtBox.SetBinding(TextBox.TextProperty, new System.Windows.Data.Binding() { Source = this.DataContext, Path = new PropertyPath(PasswordPropertyName), Mode = BindingMode.TwoWay, ValidatesOnDataErrors = true });
}
}
Here's the XAML of the control.
<UserControl x:Class="GAB.EAP2011.Controls.EAPPasswordBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400" x:Name="_root" Loaded="_root_Loaded">
<Grid x:Name="LayoutRoot" Background="White">
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Top">
<PasswordBox x:Name="pwdBox" />
<TextBox x:Name="txtBox" />
</StackPanel>
</Grid>
Here's how to use it:
<local:EAPPasswordBox x:Name="pwdBox"
Grid.Column="1" Grid.Row="0" Grid.ColumnSpan="2" PasswordPropertyName="password" ShowText="{Binding showPassword,Mode=TwoWay}"></local:EAPPasswordBox>
Now you got a nice password visibility switcher control :)
Comments appreciated!

Resources