I am trying to bind a dependency property to an INotifyPropertyChanged-enabled property with a multi-level property path.
When the owner object of the property is not null, the binding works, but if the owner object is null, the binding does not do anything (the converter is not called and TargetNullValue is not used).
Here is some minimal sample code to reproduce the problem:
Window1.xaml.cs:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.ComponentModel;
namespace NPCPropertyPath
{
public abstract class VMBase : INotifyPropertyChanged
{
protected void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (e == null) {
throw new ArgumentNullException("e");
}
if (PropertyChanged != null) {
PropertyChanged(this, e);
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public partial class Window1 : Window
{
private class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null) {
return null;
} else if (value is int) {
return (((int)value) + 15).ToString();
} else {
return "no int";
}
}
object IValueConverter.ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
private class InnerVM : VMBase
{
private int myValue;
public int MyValue {
get {
return myValue;
}
set {
if (myValue != value) {
myValue = value;
OnPropertyChanged("MyValue");
}
}
}
}
private class OuterVM : VMBase
{
private InnerVM thing;
public InnerVM Thing {
get {
return thing;
}
set {
if (thing != value) {
thing = value;
OnPropertyChanged("Thing");
}
}
}
}
private readonly OuterVM vm = new OuterVM();
public Window1()
{
InitializeComponent();
var txt = new TextBlock();
txt.SetBinding(TextBlock.TextProperty,
new Binding("Thing.MyValue") {
Source = vm,
Mode = BindingMode.OneWay,
Converter = new MyConverter(),
TargetNullValue = "(error)"
});
container.Content = txt;
var txt2 = new TextBlock();
txt2.SetBinding(TextBlock.TextProperty,
new Binding("Thing") {
Source = vm,
Mode = BindingMode.OneWay,
Converter = new MyConverter(),
TargetNullValue = "(error)"
});
container2.Content = txt2;
}
void Button_Click(object sender, RoutedEventArgs e)
{
vm.Thing = new InnerVM();
vm.Thing.MyValue += 10;
}
}
}
Window1.xaml:
<Window x:Class="NPCPropertyPath.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="NPCPropertyPath" Height="300" Width="300">
<StackPanel>
<Button Content="Change value" Click="Button_Click"/>
<ContentControl Name="container"/>
<ContentControl Name="container2"/>
</StackPanel>
</Window>
Of course, this is a significantly simplified form of my real application, where there is quite a bit more going on and the involved classes are not crammed together in two files (or even in the same assembly) where they can all see each other.
This sample displays a window with a button and two content controls. Each of the content controls contains a TextBlock whose Text property is bound to a property from a view-model. The view-model instance (of type OuterVM) is assigned to the Source property of each binding.
OuterVM implements INotifyPropertyChanged; it has a property Thing of type InnerVM (which also implements INotifyPropertyChanged), which in turn has a property MyValue.
The first text block is bound to Thing.MyValue, the second one just to Thing. Both of the bindings have a converter set, as well as a value for the TargetNullValue property that should be displayed on the text block if the target property is null.
The Thing property of the OuterVM instance is initially null. When clicking the button, something is assigned to that property.
The problem: Only the second text block displays anything initially. The one that is bound to Thing.MyValue neither invokes the converter (as evidenced by setting breakpoints), nor does it use the value of the TargetNullValue property.
Why? And how can I have the first text block display a default value instead of Thing.MyValue while Thing is not assigned?
For this purpose you should not use TargetNullValue, you should use FallbackValue property of the Binding.
Related
So I have this view model:
public class WiresharkFiles : INotifyPropertyChanged
{
public ObservableCollection<WiresharkFile> List { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private bool _inUse;
private int _packets;
private bool _hasItems;
public WiresharkFiles()
{
List = new ObservableCollection<WiresharkFile>();
HasItems = false;
List.CollectionChanged += List_CollectionChanged;
}
private void List_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
HasItems = List.Count > 0;
}
public bool InUse
{
get { return _inUse; }
set
{
_inUse = value;
NotifyPropertyChanged("InUse");
}
}
public int Packets
{
get { return _packets; }
set
{
_packets = value;
NotifyPropertyChanged("Packets");
}
}
public bool HasItems
{
get { return _hasItems; }
set
{
_hasItems = value;
NotifyPropertyChanged("HasItems");
}
}
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MainWindow.xaml
private WiresharkFiles caps;
public MainWindow()
{
InitializeComponent();
caps = new WiresharkFiles();
}
Window.Resources
<Window.Resources>
<Convertors:CollectionHasItemsConverter x:Key="CollectionHasItemsConverter"/>
</Window.Resources>
Converter
public class CollectionHasItemsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And base of my collection item (empty or not) i want to enable/disable my Button:
<Button Name="btnDeleteAll"
Click="btnDeleteAll_Click"
IsEnabled="{Binding Path=(caps.HasItems),Converter={StaticResource CollectionHasItemsConverter}}">
And i got this error:
XamlParseException: Type reference cannot find type named
'{http://schemas.microsoft.com/winfx/2006/xaml/presentation}caps'.
I don't see where you're associating your DataContext with the caps property.
Make sure you have a public property because the WPF engine isn't running from within your class and won't be able to access the private WiresharkFiles caps; variable. Try the following:
private WiresharkFiles caps;
public WiresharkFiles Files { get { return caps; } }
with a corresponding
public MainWindow()
{
InitializeComponent();
caps = new WiresharkFiles();
DataContext = Files;
}
Your XAML will then bind to Files as follows
IsEnabled="{Binding Path=HasItems}"
Update You'll need to have a look at implementing and binding to commands for the button which will make it a lot better. Look at this article for info on implementing and dealing with commands.
caps is a private variable:
private WiresharkFiles caps;
In order to bind, it would have to be a public property:
public WiresharkFiles caps {get;set;}
You would also have to set the datacontext of the window to itself. Something like:
this.DataContext = this;
or
In your window tag put:
DataContext="{Binding RelativeSource={RelativeSource Self}}"
I don't see how this relates to your initial question but you can use dot notation in binding.
You can bind:
{Binding AnObservableCollection.Count}
And you can compare that to 0 in a datatrigger. With a button and a bound command if you want to disable it then I'd use the canexecute of icommand and return false if you have no entries or whatever your logic is.
I have the following XAML that uses a classes CurrencyImg property to look up a static resource at runtime, as there are a lot of these in a treeview, I don't want to have to load the same image 1000 times, I keep it in a resourcedictionary so it is only loaded once.
<Image Name="imgCurrency" Grid.Column="5" Margin="0,0,0,0" Source="{w:ImageStaticResource {Binding CurrencyImg}}" Height="22" VerticalAlignment="Top"/>
With a sample resource dictionary entry as
<ImageSource x:Key="..\Resources\Images\USD.ico">../Resources/Images/USD.ico</ImageSource>
The property is as follows
public string CurrencyImg
{
get
{
if (DisplayUSDRate)
{
return AppString.General.ImagePath + AppString.CurrencyId.USD + ".ico";
}
else
{
return AppString.General.ImagePath + curr + ".ico";
}
}
}
And the property action which does the work
public override bool DisplayUSDRate
{
get { return _customer.DisplayUSDRate; }
set
{
_customer.DisplayUSDRate = value;
OnPropertyChanged("CurrencyImg");
}
}
Now when this runs, the OnPropertyChanged("CurrencyImg") does nothing, and it's clear why. In the XAML I'm not directly binding to the CurrencyImg property, I'm using it as a parameter to my StaticResourceExtension class, so when I call OnPropertyChanged, it thinks there are no bound properties to update, so the XAML image does not update. I can understand this, but obviously this does not help me as what I need to happen is the following.
1) For the class to determine which currency image gets displayed for that row
2) For the images to come from a resource dictionary so they don't get loaded more than once or performance hugely suffers
3) For changes to the DisplayUSDRate property to then reflect by updating the flag to the appropriate image. This is the bit I can't figure out how to do with my StaticResourceExtension (code below)
using System;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
namespace Citi.Rbcs.UI.Windows
{
public class ImageStaticResource : StaticResourceExtension
{
public Binding Binding { get; set; }
private static readonly DependencyProperty DummyProperty;
public ImageStaticResource()
{
}
public ImageStaticResource(Binding binding)
{
Binding = binding;
}
static ImageStaticResource()
{
DummyProperty = DependencyProperty.RegisterAttached(
"Dummy",
typeof (Object),
typeof (DependencyObject),
new UIPropertyMetadata(null));
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var target = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
var targetObject = (FrameworkElement)target.TargetObject;
Binding.Source = targetObject.DataContext;
var DummyDO = new DependencyObject();
BindingOperations.SetBinding(DummyDO, DummyProperty, Binding);
ResourceKey = DummyDO.GetValue(DummyProperty);
var resourceDictionary = new ResourceDictionary
{
Source = new Uri("pack://application:,,,/Windows/Images.xaml")
};
var key = (string) ResourceKey;
if (!resourceDictionary.Contains(key)) ResourceKey = "Default";
return base.ProvideValue(serviceProvider);
}
}
}
You could write a very simple binding converter that converts image URI strings to cached ImageSource objects:
public class StringToImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var imageUrl = value as string;
var image = MemoryCache.Default.Get(imageUrl) as ImageSource;
if (image == null)
{
image = new BitmapImage(new Uri(imageUrl));
MemoryCache.Default.Set(imageUrl, image, new CacheItemPolicy());
}
return image;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I have a TextBox ( TextBoxConsumer ) and i would like to enable a button in my UI when the length of the TextBox.Text is greater than 3,
i digged it down to
IsEnabled="{Binding
ElementName=TextBoxConsumer,
Path=Text.Length}"
for my button's IsEnabled Property but im not sure how to find the length and convert it to bool depending on the length of the text box how do i do it?
i would like to do it entirely in Xaml instead of code using Binding instead of code
I would prefer to use an IValueConverter class for this. I'll provide some quick code though its not exactly what you are looking for you should be able to tweak it.
In a cs file by itself:
using System;
using System.Globalization;
using System.Windows.Data;
public class IntCorrectAnswerToTrueFalseConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (int)value > 0;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? 1 : 0;
}
}
In App.xaml, add this line to the ResourceDictionary:
<app:IntCorrectAnswerToTrueFalseConverter x:Key="IntCorrectAnswerToTrueFalseConverter" />
Then in the xaml of where you use it:
<CheckBox
x:Name="answerCheckBox"
IsChecked="{Binding Score, Converter={StaticResource IntCorrectAnswerToTrueFalseConverter}}"
Click="CheckBoxChecked"/>
I did something similar using a tutorial similar to this using the INotifyPropertyChanged interface. I assume you have a model you are using for binding to the UI. You have a string member (like TextBoxConsumerString) which binds to you textbox. Now you need to add a boolean like TextBoxConsumerEnabled which you will set inside of the setter of TextBoxConsumerString and call the notify changed method.
this.OnPropertyChanged( new PropertyChangedEventArgs( "TextBoxConsumerEnabled" ) );
Here is an example:
public class TextBoxConsumerModel : INotifyPropertyChanged
{
private string _textBoxConsumerString;
public event PropertyChangedEventHandler PropertyChanged;
public string TextBoxConsumerString
{
get
{
return _textBoxConsumerString;
}
set
{
if (_textBoxConsumerString == value)
return;
TextBoxConsumerEnabled = value != null && value.Length > 3;
_textBoxConsumerString = value;
OnPropertyChanged(new PropertyChangedEventArgs("TextBoxConsumerEnabled"));
}
}
public bool TextBoxConsumerEnabled { get; set; }
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, e);
}
}
That should be it as far as the model goes. Now you just need to bind to the two model properties from the XAML.
Silverlight 3 app with a TabControl bound to an ObservableCollection using an IValueConverter. Initial the binding works (converter called) on app startup. Changes, Clear() or Add(), to the bound collection are not reflected in the TabControl... converter not called.
note: the bound ListBox reflects the changes to the bound collection while the TabControl does not.
Ideas?
/jhd
The XAML binding...
<UserControl.Resources>
<local:ViewModel x:Key="TheViewModel"/>
<local:TabConverter x:Key="TabConverter" />
</UserControl.Resources>
<StackPanel DataContext="{StaticResource TheViewModel}">
<ListBox ItemsSource="{Binding Classnames}" />
<controls:TabControl x:Name="TheTabControl"
ItemsSource="{Binding Classnames, Converter={StaticResource TabConverter}, ConverterParameter=SomeParameter}"/>
<Button Click="Button_Click" Content="Change ObservableCollection" />
</StackPanel>
The ViewModel...
namespace DatabindingSpike
{
public class ViewModel
{
private ObservableCollection<string> _classnames = new ObservableCollection<string>();
public ViewModel()
{
_classnames.Add("default 1 of 2");
_classnames.Add("default 2 of 2");
}
public ObservableCollection<string> Classnames
{
get { return _classnames; }
set { _classnames = value; }
}
}
}
The converter (for completeness)...
namespace DatabindingSpike
{
public class TabConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var source = value as ObservableCollection<string>;
if (source == null)
return null;
var param = parameter as string;
if (string.IsNullOrEmpty(param) || param != "SomeParameter")
throw new NotImplementedException("Null or unknow parameter pasased to the tab converter");
var tabItems = new List<TabItem>();
foreach (string classname in source)
{
var tabItem = new TabItem
{
Header = classname,
Content = new Button {Content = classname}
};
tabItems.Add(tabItem);
}
return tabItems;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Update 8/19
The concise answer is you have to implement INotifyPropertyChanged on the view model and notify listeners when the Property/Collection is changed.
Implement INotifyPropertyChanged on the ViewModel
* implement the interface INotifyPropertyChanged
* define the event (public event PropertyChangedEventHandler PropertyChanged)
* subscribe to the CollectionChanged event (Classnames.CollectionChanged += ...)
* fire the event for listeners
Best,
/jhd
ViewModel update per above... ValueConverter now called on all changes to the Property/Collection
public class ViewModel : INotifyPropertyChanged
{
private readonly ObservableCollection<string> _classnames = new ObservableCollection<string>();
public ViewModel()
{
Classnames.CollectionChanged += Classnames_CollectionChanged;
}
public event PropertyChangedEventHandler PropertyChanged;
private void Classnames_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
NotifyPropertyChanged("Classnames");
}
private void NotifyPropertyChanged(string info)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
foreach (PropertyChangedEventHandler d in handler.GetInvocationList())
{
d(this, new PropertyChangedEventArgs(info));
}
}
}
public ObservableCollection<string> Classnames
{
get { return _classnames; }
}
}
The XAML binding...
<UserControl.Resources>
<local:ViewModel x:Key="TheViewModel"/>
<local:TabConverter x:Key="TabConverter" />
</UserControl.Resources>
<StackPanel DataContext="{StaticResource TheViewModel}">
<ListBox ItemsSource="{Binding Classnames}" />
<controls:TabControl x:Name="TheTabControl"
ItemsSource="{Binding Classnames, Converter={StaticResource TabConverter}, ConverterParameter={StaticResource TheViewModel}}"/>
<Button Click="Button_Click" Content="Change Classnames" />
</StackPanel>
The ValueConverter (basically unchanged
public class TabConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var source = value as ObservableCollection<string>;
if (source == null)
return null;
//also sorted out the binding syntax to pass the ViewModel as a parameter
var viewModel = parameter as ViewModel;
if (viewModel == null)
throw new ArgumentException("ConverterParameter must be ViewModel (e.g. ConverterParameter={StaticResource TheViewModel}");
var tabItems = new List<TabItem>();
foreach (string classname in source)
{
// real code dynamically loads controls by name
var tabItem = new TabItem
{
Header = "Tab " + classname,
Content = new Button {Content = "Content " + classname}
};
tabItems.Add(tabItem);
}
return tabItems;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I realize this is a slightly old question at this point, but I don't know that anyone has explained why you need to do the INotifyPropertyChanged on the bound property on your view model.
The ItemsControl itself needs to be bound to an ObservableCollection for the collection change events to cause the ItemsControl to re-evaluate. Your converter is returning a distinct List (or Observable) collection each time it is called rather than holding on to a single ObservableCollection and adding items to it. Therefore, these collections never have any of the collection changed events raised on them... they're always new, each time the binding is re-done.
Raising PropertyChanged forces the binding to be re-evaluated and re-runs your converter, returning a distinct collection and reflecting your changes.
I feel a better approach may be to do the conversion in your ViewModel rather than in a Converter. Expose an ObservableCollection of TabItem that you bind directly to and that you modify in place. The TabControl should then see changes made directly to your collection without the need to raise PropertyChanged and re-evaluate the entire binding.
[Edit - Added my approach]
ViewModel:
public class TabSampleViewModel
{
private ObservableCollection _tabItems = new ObservableCollection();
public TabSampleViewModel()
{
AddTabItem("Alpba");
AddTabItem("Beta");
}
public ObservableCollection<TabItem> TabItems
{
get
{
return _tabItems;
}
}
public void AddTabItem( string newTabItemName )
{
TabItem newTabItem = new TabItem();
newTabItem.Header = newTabItemName;
newTabItem.Content = newTabItemName;
TabItems.Add( newTabItem );
}
}
View:
<controls:TabControl ItemsSource="{Binding TabItems}"/>
Expose
public ObservableCollection<TabItem> Classnames
{
get { return _classnames; }
set { _classnames = value; }
}
If you debug the valueconverter you'll see it's not being called as often as you think it is.
The problem could be that your ValueConverter returns a List<TabItem> instead of an ObservableCollection<TabItem>. Try that one line change and see if it helps.
I have some XAML textboxes that need to allow nothing but double and some that need to allow nothing but int.
I could use Binding.ValidationRules with all its code behind, triggers, styles as described here, but isn't there a way for these simple validations to use an attribute something like this, just so nothing but that type can be typed in:
PSEUDO-CODE:
<TextBox Text="{Binding NumberOfContracts}" SimpleValidation="{x:Type sys:Integer}"/>
<TextBox Text="{Binding ContractPrice}" SimpleValidation="{x:Type sys:Double}"/>
I have always thought about something as useful as this but now that I've read your question, I got around to writing one.
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace SimpleValidation
{
public class SimpleValidator : ValidationRule
{
#region Validation Attached Property
public static Type GetValidationType(DependencyObject obj)
{
return (Type)obj.GetValue(ValidationTypeProperty);
}
public static void SetValidationType(DependencyObject obj, Type value)
{
obj.SetValue(ValidationTypeProperty, value);
}
// Using a DependencyProperty as the backing store for ValidationType. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValidationTypeProperty =
DependencyProperty.RegisterAttached("ValidationType", typeof(Type), typeof(ValidationRule), new UIPropertyMetadata(null, OnValidationTypeChanged));
private static void OnValidationTypeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
var element = obj as FrameworkElement;
if (element == null) return;
// When the element has loaded.
element.Loaded += (s, e) =>
{
// Create a new validator
var validation = new SimpleValidator(args.NewValue as Type);
// Get the binding expression for the textbox property
var binding = BindingOperations.GetBinding(obj, TextBox.TextProperty);
// If not null and doesn't already contain the validator, then add it.
if (binding != null)
if (!binding.ValidationRules.Contains(validation))
binding.ValidationRules.Add(validation);
};
}
#endregion
#region Validation
public SimpleValidator() { }
public SimpleValidator(Type validationType)
{
ValidationType = validationType;
}
public Type ValidationType { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
try
{
// Try to convert to the type specified
Convert.ChangeType(value, ValidationType);
// Accept value
return new ValidationResult(true, "Valid value");
}
catch (Exception)
{
// return invalid type error.
return new ValidationResult(false, "Value is not of type " + ValidationType.FullName);
}
}
#endregion
}
}
Usage example:
<TextBox Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}" validator:SimpleValidator.ValidationType="{x:Type system:Double}" />
Make sure UpdateSourceTrigger reflects the type of update you require, usually PropertyChanged is best. I hope you enjoy it as much as I writing it. Full source code here.