WPF ToggleButton multibinding of IsChecked through MarkupExtension throws - wpf

I am trying to bind the IsChecked property of a ToggleButton through a markup extension which resolves to a MultiBinding. In XAML I have two source toggle buttons and a target toggle button which should have its IsChecked bound to the IsChecked of the source buttons.
<ToggleButton x:Name="Source1" Content="Source 1" Margin="5" Padding="5,2"/>
<ToggleButton x:Name="Source2" Content="Source 2" Margin="5" Padding="5,2"/>
<ToggleButton Content="Target" Margin="5" Padding="5,2">
<ToggleButton.IsChecked>
<local:ExMultiBinding Converter="{StaticResource AnyConverter}">
<Binding ElementName="Source1" Path="IsChecked"/>
<Binding ElementName="Source2" Path="IsChecked"/>
</local:ExMultiBinding>
</ToggleButton.IsChecked>
</ToggleButton>
My markup extension providing the MultiBinding looks like this
[ContentProperty("Bindings")]
public class ExMultiBindingExtension : MarkupExtension, INotifyPropertyChanged
{
public Collection<BindingBase> Bindings { get; set; } = new Collection<BindingBase>();
public IMultiValueConverter Converter { get; set; }
public BindingMode Mode { get; set; }
public UpdateSourceTrigger UpdateSourceTrigger { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
var multi = new MultiBinding
{
Converter = Converter,
Mode = Mode,
UpdateSourceTrigger = UpdateSourceTrigger
};
return multi;
}
public event PropertyChangedEventHandler PropertyChanged;
}
Starting the UI yields
ArgumentException: 'System.Windows.Data.MultiBinding' is not a valid value for property 'IsChecked'.
I checked and the IsChecked property on the ToggleButton implementation is actually not marked with
[Bindable(true)]
but only with
[Category("Appearance")]
[TypeConverter(typeof (NullableBoolConverter))]
[Localizability(LocalizationCategory.None, Readability = Readability.Unreadable)]
Direct binding through a Binding or MultiBinding in XAML works.
The example code is simplified to highlight the problem. It is not possible to avoid the MarkupExtension in the real code since the implementation is more complicated.

BindingBase itself is a MarkupExtension. And of course a markupExtension is not of type bool and can't be assigned to a member of type bool.
The reason why your extension is called is because the XAML parser wants to resolve every MarkupExtension. It's done by invoking the MarkupExtension.ProvideValue method. In this case the BindingBase object will be associated with a BindingExpressionBase object. This BindingExpressionBase will resolve the underlying binding to return the actual value (simplified).
So you have to manually resolve the BindingBase markup:
public override object ProvideValue(IServiceProvider serviceProvider)
{
var multi = new MultiBinding
{
Converter = Converter,
Mode = Mode,
UpdateSourceTrigger = UpdateSourceTrigger
};
// Resolve the MarkupExtension BindingBase
return multi.ProvideValue(serviceProvider);
}

Related

Default value at design time XAML

I have a binded TextBlock, XAML:
<TextBlock Text="{Binding MyText}"/>
I know the FallbackValue can be used if the Binding isn't available, but this happens at run time ? Is there any way to show a default value at design time ? It would make things easier if I could see a value when designing my windows instead of an empty TextBlock.
Thanks
Update: Visual Studio 2019 v16.7
You can now do:
<TextBlock Text="{Binding MyText}" d:Text="Design time value"/>
If you would prefer a less verbose version of Ian Bamforth's answer, you can just do
<TextBlock Text="{Binding MyText, FallbackValue=None}"/>
Adapting an example from this question.
This works for me - the text "None" is shown in the designer:
<TextBlock>
<TextBlock.Text>
<Binding ElementName="root" Path="blah" FallbackValue="None" />
</TextBlock.Text>
</TextBlock>
Hope that helps
Using FallbackValue is wrong, because it also affects the runtime behavior (the fallback value is used if the binding fails to obtain a value from the source).
I came up with a custom markup extension that mimics Binding (ideally I would have preferred to inherit from Binding, but the ProvideValue method is not virtual...):
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
namespace MyNamespace
{
public class BindingEx : MarkupExtension
{
private readonly Binding _binding;
public BindingEx()
{
_binding = new Binding();
}
public BindingEx(string path)
{
_binding = new Binding(path);
}
public PropertyPath Path
{
get => _binding.Path;
set => _binding.Path = value;
}
public BindingMode Mode
{
get => _binding.Mode;
set => _binding.Mode = value;
}
public RelativeSource RelativeSource
{
get => _binding.RelativeSource;
set => _binding.RelativeSource = value;
}
public string ElementName
{
get => _binding.ElementName;
set => _binding.ElementName = value;
}
public IValueConverter Converter
{
get => _binding.Converter;
set => _binding.Converter = value;
}
public object DesignValue { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
var target = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
if (target.TargetObject is DependencyObject d && DesignerProperties.GetIsInDesignMode(d))
return DesignValue;
return _binding.ProvideValue(serviceProvider);
}
}
}
You can use it just like Binding, with the addition of the DesignValue property:
<TextBlock Text="{my:BindingEx Name, DesignValue=John Doe}" />
Note that BindingEx doesn't have all the properties from Binding, but you can easily add them if necessary.
If you have this data bound and are using the MVVM architecture then setting a DEFAULT value for the model item it is bound to will display the value at design time
I am just using:
Model.cs:
private int frame = 999999;
public int Frame
{
get { return frame; }
set
{
frame = value;
NotifyPropertyChanged(m => m.Frame);
}
}
and in my XAML:
<TextBlock Text="{Binding Path=Frame}" />
and the default value of "999999" is being displayed in the designer

bind a control to a code-behind property of this object

I have a property in a code-behind class to which I want to bind my Label control:
public MainWindow()
{
InitializeComponent();
this.Label1Content = "some text";
}
public string Label1Content { get; set; }
But the binding fails. Obviously I am missing something in the binding configuration, but I don't know what. I know how to bind this property using C#, but how do I bind it using XAML and without declaring DataContext?
If you don't want to declare a datacontext anywhere, you could use
<Label Content="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=Label1Content}" />
You still have to declare a DataContext, even if it is the same control:
public MainWindow()
{
InitializeComponent();
DataContext = this;
this.Label1Content = "some text";
}
Also, the control will have to implement INotifyPropertyChanged so you can raise the PropertyChanged event. You're property should be self-contained like so:
public string _lable1Content;
public string Label1Content
{
get { return _label1Content; }
set
{
if (Equals(value, _label1Content)) return;
_label1Content = value;
//However you decide to implement the RaisePropertyChanged method.
}
}

How to Databind to a CLR object with Source different to path

I am trying to update a textblock on the view by databinding to a property in the viewmodel (the datacontext for the view).
In the code below; when SelectedItem changes, I want the textblock text to update with the value of the Name property on SelectedItem.
In an attempt to achieve this I have set the binding source to the property that is changing and the binding path to the data I want to update the textblock with.
I.e. I am expecting that the binding engine will see a change on the binding Source (SelectedItem) and pull the data from the binding Path (SelectedItem.Name).
http://msdn.microsoft.com/en-us/library/ms746695.aspx
Setting the SelectedItem raises INPC but the text does not update.
public class ViewModel
{
public IConfiguration Configuration { get; set;}
}
public class Configuration : IConfiguration, INotifyPropertyChanged
{
public Item SelectedItem
{
get { return _item;}
set
{
_item = value;
ItemName = _item.Name;
RaisePropertyChangedEvent("SelectedItem");
}
}
public string ItemName
{
get { return _itemName;}
set
{
_itemName= value;
RaisePropertyChangedEvent("ItemName");
}
}
}
public class Item
{
public string Name { get; set;}
}
I know that changes on Configuration are seen because this works:
<TextBlock Text="{Binding Configuration.ItemName}"/>
But this does not:
<TextBlock Text="{Binding Path=Name, Source=Configuration.SelectedItem}"/>
And nor does this:
<TextBlock Text="{Binding Path=Configuration.SelectedItem.Name, Source=Configuration.SelectedItem}"/>
I'm assuming that this should be straightforward - what have I missed?
I've never actually seen anyone use Binding.Source before, so I don't know much about it. But my guess is that it's not dynamic. When you create your binding, it's grabbing a reference to the object specified in your Source, and then that's it: it uses that same reference for the lifetime of the binding.
Why make this complicated? Just use Path. That's the normal way of doing binding, and it's dynamic all the way -- what you're doing is exactly what Path is intended for.
<TextBlock Text="{Binding Path=Configuration.SelectedItem.Name}"/>
This is probably working, you just can not see it. The Binding engine has not been notified that the Name property of the Item object has changed.
Try implementing the INotifyPropertyChanged interface on the Item class as well (raising the PropertyChanged event as necessary)
This will work for your third binding situation, and also for a similar definition as below
<TextBlock DataContext="{Binding Path=Configuration.SelectedItem}" Text="{Binding Path=Name}"/>
But for a simpler fix, this should work:
<TextBlock Text="{Binding Path=Configuration.SelectedItem.Name}" />
Edit:
public class Configuration : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
private Item _SelectedItem = null;
public Item SelectedItem
{
get
{
return _SelectedItem;
}
set
{
_SelectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
}
public class Item
{
public string Name { get; set; }
}
Then in a Command Execute somewhere I have this:
Configuration.SelectedItem = new Item() { Name = "test" };
Which updates the TextBlock in the View fine:
<TextBlock Text="{Binding Path=Configuration.SelectedItem.Name}" />

WPF- Is there a way to bind to the SelectedValues of both a TreeView and a ListBox?

I need to bind so that the Content of a content control is set to the SelectedValue of either the TreeView or the ListBox. The SelectedValue that was most recently changed should provide the content for the ContentControl.
I was able to get this working using the following concept.
Bind the content control to a read only property "SelectedItem" (with private property _selectedItem).
Bind the ListBox.SelectedItem to a read/write property "SelectedItemLB".
In the SelectedItemLB setter, set the value of _selectedItem, and raise the PropertyChanged event for SelectedItem.
Create a handler for VreeView.SelectedItemChanged, which sets the value of _selectedItem and raises the PropertyChanged event for SelectedItem.
Here is my full code:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
this.items = new List<object>();
this.items.Add(new Car("Green"));
this.items.Add(new Car("Blue"));
this.items.Add(new Car("Red"));
this._selectedItem = this.items[0];
this.treeView1.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(treeView1_SelectedItemChanged);
this.DataContext = this;
}
void treeView1_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
this._selectedItem = treeView1.SelectedItem;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
}
private List<object> items;
public List<object> Items
{
get { return items; }
set { items = value; }
}
public object SelectedItemLB
{
get { return _selectedItem; }
set
{
_selectedItem = value;
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
}
}
}
private object _selectedItem;
public object SelectedItem
{
get { return _selectedItem; }
}
public event PropertyChangedEventHandler PropertyChanged;
}
The XAML is pretty simple:
<StackPanel>
<ListBox Name="listBox1" ItemsSource="{Binding Path=Items}" SelectedItem="{Binding Path=SelectedItemLB, Mode=TwoWay}" ></ListBox>
<TreeView Name="treeView1" ItemsSource="{Binding Path=Items}">
<TreeView.Resources>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected}"></Setter>
</Style>
</TreeView.Resources>
</TreeView>
<ContentControl Content="{Binding Path=SelectedItem.Color}"></ContentControl>
</StackPanel>
I can't think of a way to do that directly. However there are several straightforward solutions.
A. Use events to set the Content
Simply attach a common handler to the SelectedValueChanged events of your ItemsControls. Whenever one of them changes its selection, the handler will set the Content to whatever was selected. I think this is most simple.
B. Use intermediary properties
Bind the SelectedValue of each ItemsControl to a property. In the property's setter, also set the Content equal to value. This allows you to use data binding instead of event handlers, but it still requires you to write code-behind and it doesn't buy you much. Of course, if you are already binding to properties for other purposes, there is almost no extra cost (only an assignment in each setter) so this method might be preferable.

WPF: How to bind to a nested property?

I can bind to a property, but not a property within another property. Why not? e.g.
<Window DataContext="{Binding RelativeSource={RelativeSource Self}}"...>
...
<!--Doesn't work-->
<TextBox Text="{Binding Path=ParentProperty.ChildProperty,Mode=TwoWay}"
Width="30"/>
(Note: I'm not trying to do master-details or anything. Both properties are standard CLR properties.)
Update: the problem was that my ParentProperty depended on an object in XAML being initialized. Unfortunately that object was defined later in the XAML file than the Binding, so the object was null at the time when my ParentProperty was read by the Binding. Since rearranging the XAML file would screw up the layout, the only solution I could think of was to define the Binding in code-behind:
<TextBox x:Name="txt" Width="30"/>
// after calling InitializeComponent()
txt.SetBinding(TextBox.TextProperty, "ParentProperty.ChildProperty");
You can also set DataContext for TextBox in XAML (I don't know if it's optimal solution, but at least you don't have to do anything manually in codeBehind except of implementing INotifyPropertyChanged). When your TextBox has already DataContext (inherited DataContext) you write code like this:
<TextBox
DataContext="{Binding Path=ParentProperty}"
Text="{Binding Path=ChildProperty, Mode=TwoWay}"
Width="30"/>
Be aware that until your DataContext for TextBox isn't ready binding for Text property will not be 'established' - you can add FallbackValue='error' as Binding parameter - it will be something like indicator which will show you if binding is OK or not.
All I can think of is that the ParentProperty is being changed after the Binding is created, and it does not support change notification. Every property in the chain must support change notification, whether it be by virtue of being a DependencyProperty, or by implementing INotifyPropertyChanged.
Do both the ParentProperty and your class implement INotifyPropertyChanged?
public class ParentProperty : INotifyPropertyChanged
{
private string m_ChildProperty;
public string ChildProperty
{
get
{
return this.m_ChildProperty;
}
set
{
if (value != this.m_ChildProperty)
{
this.m_ChildProperty = value;
NotifyPropertyChanged("ChildProperty");
}
}
}
#region INotifyPropertyChanged Members
#endregion
}
public partial class TestClass : INotifyPropertyChanged
{
private ParentProperty m_ParentProperty;
public ParentProperty ParentProperty
{
get
{
return this.m_ParentProperty;
}
set
{
if (value != this.m_ParentProperty)
{
this.m_ParentProperty = value;
NotifyPropertyChanged("ParentProperty");
}
}
}
}
public TestClass()
{
InitializeComponent();
DataContext = this;
ParentProperty = new ParentProperty();
ParentProperty.ChildProperty = new ChildProperty();
#region INotifyPropertyChanged Members
#endregion
}

Resources