Firing PropertyChanged event when data is bound without path - c#

I have a Listbox bound to an ObservableCollection of ImageMetadata class. Item template of Listbox is defined as
<Image Source="{Binding Converter={StaticResource ImageConverter}}" />
ImageConverter is written as
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var metadata = (ImageMetadata)value;
if (metadata.IsPublic)
{
//code to return the image from path
}
else
{
//return default image
}
}
ImageMetadata is the 'Model' class written as
class ImageMetadata : INotifyPropertyChanged
{
public string ImagePath
{
......
}
public bool IsPublic
{
......
}
}
When an image is updated, I will trigger PropertyChanged event as given below
NotifyPropertyChanged("ImagePath");
Problem here is that : NotifyPropertyChanged event will not work since I am specifying the changed property name as 'ImagePath' and the binding is to 'ImageMetadata' object rather than 'ImagePath' property.
I cannot use
<Image Source="{Binding ImagePath, Converter={StaticResource ImageConverter}}" />
since I need the IsPublic property also to decide which image to display.
How can I modify the code to properly fire PropertyChanged event?
Edit : I am developing for Windows phone 8.

You could use a MultiBinding with a multi-value converter:
<Image>
<Image.Source>
<MultiBinding Converter="{StaticResource ImageConverter}">
<Binding Path="ImagePath"/>
<Binding Path="IsPublic"/>
</MultiBinding>
</Image.Source>
</Image>
The Convert method would look like this:
public object Convert(
object[] values, Type targetType, object parameter,CultureInfo culture)
{
object result = null;
if (values.Length == 2 && values[0] is string && values[1] is bool)
{
var imagePath = (string)values[0];
var isPublic = (bool)values[1];
...
}
return result;
}

Related

ContentStringFormat binding doesn't refresh on propertychange

I'm having an issue causing a label to refresh when it's content doesn't change but its format does. The ContentStringFormat property is bound to the viewmodel and the property change is notified, but the label doesn't update, please find bellow a minimal reproduction exemple in code as well as a project ready to compile/run that demonstrates the issue.
Download project : https://www.dropbox.com/s/rjs1lot09uc2lgj/WPFFormatBindingRefresh.zip?dl=0
XAML :
<StackPanel>
<Label Content="{Binding FirstLabelContent}"></Label>
<Label Content="{Binding SecondLabelContent}" ContentStringFormat="{Binding SecondLabelFormatContent}"></Label>
<Button Click="Button_Click">Add "test" to all bound elements</Button>
</StackPanel>
Code behind :
public event PropertyChangedEventHandler PropertyChanged = (a,b)=> { }; // empty handler avoids checking for null when raising
public string FirstLabelContent { get; set; } = "First Label";
public string SecondLabelContent { get; set; } = "Second";
public string SecondLabelFormatContent { get; set; } = "{0} Label";
void PropertyChange(string PropertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
private void Button_Click(object sender, RoutedEventArgs e)
{
FirstLabelContent += " TEST";
SecondLabelFormatContent += " TEST";
PropertyChange("FirstLabelContent"); // First label correctly updates
PropertyChange("SecondLabelFormatContent"); // Second label doesn't update, expected behavior is changing the format string should cause an update
}
The Label doesn't support refreshing the ContentStringFormat through a binding.
You could use a multi converter like this:
public class MultiConverter2 : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
string SecondLabelContent = values[0] as string;
string SecondLabelFormatContent = values[1] as string;
return string.Format(SecondLabelFormatContent, SecondLabelContent);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
XAML:
<StackPanel>
<StackPanel.Resources>
<local:MultiConverter2 x:Key="conv" />
</StackPanel.Resources>
<Label Content="{Binding FirstLabelContent}"></Label>
<Label>
<Label.Content>
<MultiBinding Converter="{StaticResource conv}">
<Binding Path="SecondLabelContent" />
<Binding Path="SecondLabelFormatContent" />
</MultiBinding>
</Label.Content>
</Label>
<Button Click="Button_Click">Add "test" to all bound elements</Button>
</StackPanel>

How to get in ConvertBack parameters from Convert?

The problem:
I am using MultiBinding with converter to pass (x,y) coordinates into method.
And I can't make it working in back direction:
public class MyConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var x = (int)values[0];
var y = (int)values[1];
return Model.Get(x, y);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
Model.Set(x, y, value); // how to get x, y here?
return new object[] { Binding.DoNothing, Binding.DoNothing };
}
}
Additional info:
The data will be visualized in a form of table. Here is cell template:
<TextBox>
<TextBox.Text>
<MultiBinding Converter="{StaticResource converter}" Mode="TwoWay">
<Binding Path="X" Mode="OneWay" />
<Binding Path="Y" Mode="OneWay" RelativeSource="..." />
</MultiBinding>
</TextBox.Text>
</TextBox>
The idea is to use converter, which receive x (from cell view model) and y (from parent column view model, notice RelativeSource) and calls Get(x,y) to display value.
However, when user entered something, ConvertBack is called and I need to call Set(x, y, value) method.
How do I pass x and y into ConvertBack?
There might be more-or-less dirty workarounds to get such a multivalue converter working. But I'd suggest you keep your multivalue converter one-way, but return a container object that wraps the actual text property.
Instead of directly binding to the TextBox.Text property, bind to some other property (eg. DataContext or Tag) and then bind the text to the container value.
Small example:
<TextBox Text="{Binding Value}">
<TextBox.DataContext>
<MultiBinding Converter="{StaticResource cMyConverter}">
<Binding Path="X"/>
<Binding Path="Y"/>
</MultiBinding>
</TextBox.DataContext>
</TextBox>
With container and converter:
public class ValueProxy
{
public int X { get; set; }
public int Y { get; set; }
public string Value
{
get { return Model.Get(X, Y); }
set { Model.Set(X, Y, value); }
}
}
public class MyConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var x = (int)values[0];
var y = (int)values[1];
return new ValueProxy { X = x, Y = y };
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return new object[] { Binding.DoNothing, Binding.DoNothing };
}
}
The short answer is that you can't directly get the values of xand y inside your ConvertBack method. The IMultiValueConverter Convert multiple values into a single value. So, the ConvertBack method will do the opposite: convert a single value into multiple values.
It all depends on what your Model.Get(x, y) method returns. It needs to return a value that is unique enough for you to get the separate values of x and y from it.
Example: create unique strings for each pair of (x,y).
It seems hard to pass parameters into ConvertBack. It might be possible, but there is a workaround, which makes ConvertBack unnecessary. Thanks to #XAMlMAX for an idea.
One possibility to achieve it (there could be a better way) is to use data templates. Instead of multi-binding TextBlock.Text with string we can bind ContentControl.Content with some viewmodel, and this viewmodel should do the rest, including Set(x, y, value) call.
Here is code:
public class ViewModel
{
public int X { get; set; }
public int Y { get; set; }
string _text;
public string Text
{
get { return _text; }
set
{
// this should be only called by the view
_text = value;
Model.Set(X, Y, value);
}
}
public ViewModel(string text)
{
_text = text;
}
}
public class MyConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var x = (int)values[0];
var y = (int)values[1];
return new ViewModel(Model.Get(x, y)) { X = x, Y = y };
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
and the xaml will become
<ContentControl Focusable="False">
<ContentControl.Content>
<MultiBinding Converter="{StaticResource converter}">
<Binding Path="X" />
<Binding Path="Y" RelativeSource="..."/>
</MultiBinding>
</ContentControl.Content>
</ContentControl>
where data template is
<DataTemplate DataType="{x:Type local:ViewModel}">
<TextBox Text="{Binding Text}" />
</DataTemplate>

MVVM bind IsEnabled to multiple bools in ViewModel

I have the button below which has it's IsEnabled property bound to a bool in the ViewModel called EnableBtn.
If I have another bool called EnableMail how would I amend this so that the IsEnabled is bound to both?
<Button IsEnabled="{Binding EnableBtn, Converter={StaticResource InvertBooleanConverter}}" x:Name="SaveSendButton" Grid.Row="0" Grid.Column="1" Text="{i18n:Translate SaveAndSend}" Style="{StaticResource bottomButtonsBlue}" Command="{Binding EmailPlanCommand}"></Button>
public bool IsBothEnabled
{
get
{
if (EnableBtn && EnableMail)
return true;
return false;
}
}
Now bind your Button.IsEnabled Property to IsBothEnabled.
Alternative to the valid solution from meq, you could use a multi binding:
The XAML code would look like:
<Button.IsEnabled>
<MultiBinding Converter="{StaticResource AreAllTrueMultiValueConverter}">
<Binding Path="EnableBtn" />
<Binding Path="EnableMail" />
</MultiBinding>
</TextBox.IsEnabled>
However, you need a MultiValueConverter similar to:
public class AreAllTrueMultiValueConverter: IMultiValueConverter
{
public object Convert(object[] values, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
return values.OfType<bool>().All();
}
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException("Cannot convert back");
}
}
I would prefer the MultiBinding to the additional view model property because it doesn't require "dependent properties" that has to be notified if another property changed. Therefore it results in simpler view model logic.

WPF Databinding fails at binding entire object

In the following DataTemplate, the first binding doesn't work while the 2nd one works, and I would like to know why.
<local:IsEnabledConverter x:Key="isEnabled"/>
<local:Boolean2TextConverter x:Key="txtConverter"/>
<DataTemplate x:Key="fileinfoTemplate" DataType="{x:Type local:MyFileInfo}">
<StackPanel>
<Label x:Name="1stLabel" Content="{Binding Path=Filename}" IsEnabled="{Binding Path=., Converter={StaticResource isEnabled}}"/> <--- doesn't work
<Label x:Name="2ndLabel" Content="{Binding Path=IfPrint, Converter={StaticResource txtConverter}}" IsEnabled="{Binding Path=IsChecked, ElementName=ckBox}"/> <--- works
<CheckBox x:Name="ckBox" IsChecked="{Binding Path=IfPrint}" IsEnabled="{Binding Path=IsValid}" Style="{StaticResource printCkBox}"/>
</StackPanel>
</DataTemplate>
IsEnabledConverter:
class IsEnabledConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
MyFileInfo f = value as MyFileInfo;
return f.IsValid && f.IfPrint;
}
//... omit ConvertBack NotImplementedException stuff
}
Boolean2TextConverter:
class IsEnabledConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
Boolean b = (Boolean)value;
return b.ToString();
}
//similarly omit ConvertBack here
}
Code for MyFileInfo:
public class MyFileInfo {
public string IfPrint {
get;
set;
}
public string IsValid {
get;
set;
}
...
}
Problem: When the CheckBox is toggled, the 2nd Label grays out and shows "false", or becomes normal and shows "true", as it should. However, the first Label doesn't change at all; its IsEnabled state is supposed be the conjunction of two Booleans, one of which is changed by the CheckBox. What is wrong? (note that the IsEnabledConverter is called once upon GUI initialization, but not called again when its binding source changes.)
There are 2 issues here. First you have to implement INotifyPropertyChanged for the ViewModel MyFileInfo. Secondly you have to use MultiBinding here. Because I don't think we have some way to trigger updating the target (such as when toggling the CheckBox) if you bind the whole view model to the IsEnabled target. So here is how it should be done:
Your view model:
public class MyFileInfo : INotifyPropertyChanged {
bool _ifPrint;
bool _isValid;
public bool IfPrint {
get { return _ifPrint; }
set {
if(_ifPrint != value) {
_ifPrint = value;
OnPropertyChanged("IfPrint");
}
}
}
public bool IsValid {
get { return _isValid; }
set {
if(_isValid != value) {
_isValid = value;
OnPropertyChanged("IsValid");
}
}
}
//Implement INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string prop){
var handler = PropertyChanged;
if(handler != null) handler(this, new PropertyChangedEventArgs(prop));
}
//.... should do the same for the remaining properties....
//...
}
Here is the converter used for MultiBinding, which should implement IMultiValueConverter (instead of IValueConverter):
class IsEnabledConverter : IMultiValueConverter {
public object Convert(object[] values, Type targetType, object parameter,
System.Globalization.CultureInfo culture) {
if(values.Length == 2){
return (bool) values[0] && (bool) values[1];
}
return false;
}
//... omit ConvertBack NotImplementedException stuff
}
Here is the modifed XAML (to use MultiBinding instead):
<Label x:Name="firstLabel" Content="{Binding Path=Filename}">
<Label.IsEnabled>
<MultiBinding Converter="{StaticResource isEnabled}">
<Binding Path="IsValid"/>
<Binding Path="IfPrint"/>
</MultiBinding>
</Label.IsEnabled>
</Label>
Now one of IsValid and IfPrint changing will trigger the MultiBinding's Converter. Here you can also bind to IsChecked of the CheckBox directly instead of indirectly via IfPrint.
PS: Note Name used in XAML (as well as in codebehind) must not start with number.
Since the instance of MyFileInfo does not change while you check/uncheck the checkbox hence IsEnabledConverteris not getting called.
In order to Enable/Disable your 1stLabel depending on two properties, either use MultiValueConverter or use MultiDataTrigger by applying Style to your Label.

Philosophy on Binding to Parameters in WPF

So I'm trying to build out a project that will allow a user to type some text into a textbox on the left side of the form and that will filter out the available items from my datasource list.
<Label Content="Enter item name below"></Label>
<TextBox Name="SearchTermTextBox" TabIndex="0" Text="" />
I was under the impression I could bind to the datasource the list then use a converter to filter out the items that were unlike the string.
<ListBox DataContext="{Binding Colors}">
<ListBox.ItemsSource>
<MultiBinding Converter="{StaticResource FilterTextValueConverter}" ConverterParameter="{Binding ElementName=SearchTermTextBox, Path=Text}" />
</ListBox.ItemsSource>
<ListBox.ItemTemplate>
//etc...
</ListBox.ItemTemplate>
</ListBox>
However, you can't bind to an elementname in the converterparameter unless you use something called a dependency property.
Edit: Seeing as I've created confusion with the code above, here's the converter I'm trying to bind:
public class FilterTextValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var trackedColors = value as List<Colors>;
if (trackedColors != null)
return (trackedColors).Where(item => item.ColorName.Contains(parameter.ToString())).ToList();
return null;
}
public object ConvertBack(object value, Type targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class Colors
{
public String ColorName;
public String Description;
}
What is wrong with my approach here? Clearly I'm angering the WPF gods since this is a fairly straightforward operation but I'm being denied it on principle. Any help would be appreciated.
Simple binding with converter will work here, no need for MultiBinding.
<ListBox ItemsSource="{Binding Path=Text, ElementName=SearchTermTextBox,
Converter="{StaticResource FilterTextValueConverter}">
......
</ListBox>
Assuming FilterTextValueConverter is implementing IValueConverter, you can access text from value passed to Convert method.
public class FilterTextValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
string text = value.ToString(); // TEXT for textBox can be accessed here.
return new List<string>(); // Return filtered list from here.
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
return Binding.DoNothing;
}
}
UPDATE
In case you want to pass multiple bindings to converter, use IMultiValueConverter because ConverterParameter is not Dependency property, hence cannot be bound.
XAML
<ListBox DataContext="{Binding Colors}">
<ListBox.ItemsSource>
<MultiBinding Converter="{StaticResource FilterTextValueConverter}">
<Binding/>
<Binding ElementName="SearchTermTextBox" Path="Text"/>
</MultiBinding>
</ListBox.ItemsSource>
</ListBox>
Converter
public class FilterTextValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
var trackedColors = values[0] as List<Colors>;
if (trackedColors != null && !String.IsNullOrEmpty(values[1].ToString()))
return (trackedColors).Where(item =>
item.ColorName.Contains(values[1].ToString())).ToList();
return null;
}
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
I continued looking into this issue well after the accepted answer was posted and working for me. What I discovered is that it's a fairly trivial task to wrap the control you're trying to get a new dependencyproperty out of to allow for proper binding.
I will not be accepting my own answer to this determined so much later, but this seems (in my amateur opinion) like a much more elegant solution than adding a converter despite being a bit more complex:
Note that this is for a new dependency on the caretindex property of a textbox, not for the original question on binding, but it just requires some smart renaming to get it working ;).
public class TextBoxDependencyWrapper : TextBox
{
public static readonly DependencyProperty CaretIndexProperty = DependencyProperty.Register(
"CaretIndex", typeof (int), typeof (TextBoxDependencyWrapper), new FrameworkPropertyMetadata(default(int), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, CaretIndexChanged ));
protected override void OnKeyUp(KeyEventArgs e) //Event that changes the property we're trying to track
{
base.OnKeyUp(e);
CaretIndex = base.CaretIndex;
}
protected override void OnKeyDown(KeyEventArgs e) //Event that changes the property we're trying to track
{
base.OnKeyDown(e);
CaretIndex = base.CaretIndex;
}
public new int CaretIndex
{
get { return (int) GetValue(CaretIndexProperty); }
set { SetValue(CaretIndexProperty, value); }
}
}

Categories