WPF Attached Property Data Binding - wpf

I try to use binding with an attached property. But can't get it working.
public class Attached
{
public static DependencyProperty TestProperty =
DependencyProperty.RegisterAttached("TestProperty", typeof(bool), typeof(Attached),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Inherits));
public static bool GetTest(DependencyObject obj)
{
return (bool)obj.GetValue(TestProperty);
}
public static void SetTest(DependencyObject obj, bool value)
{
obj.SetValue(TestProperty, value);
}
}
The XAML Code:
<Window ...>
<StackPanel local:Attached.Test="true" x:Name="f">
<CheckBox local:Attached.Test="true" IsChecked="{Binding (local:Attached.Test), Mode=TwoWay, RelativeSource={RelativeSource Self}}" />
<CheckBox local:Attached.Test="true" IsChecked="{Binding (local:Attached.Test), Mode=TwoWay}" />
</StackPanel>
</Window>
And the Binding Error:
System.Windows.Data Error: 40 : BindingExpression path error: '(local:Attached.Test)' property not found on 'object' ''StackPanel' (Name='f')'. BindingExpression:Path=(local:Attached.Test); DataItem='StackPanel' (Name='f'); target element is 'CheckBox' (Name=''); target property is 'IsChecked' (type 'Nullable`1')

Believe it or not, just add Path= and use parenthesis when binding to an attached property:
IsChecked="{Binding Path=(local:Attached.Test), Mode=TwoWay, RelativeSource={RelativeSource Self}}"
In addition, your call to RegisterAttached should pass in "Test" as the property name, not "TestProperty".

I'd have preferred to post this as a comment on Kent's answer but since I don't have enough rep to do so... just wanted to point out that as of WPF 4.5, adding Path= isn't necessary anymore. However the attached property name still needs to be wrapped with parentheses.

Putting a bracket works. I had to do automation id binding of a parent contentcontrol to a textblock in datatemplate. Automation Id is an attached property.
I put the property in brackets and binding worked.
AutomationProperties.AutomationId="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ContentControl},Path=(AutomationProperties.AutomationId)}"

Related

Why can't the Grid find the DependencyProperty on the host UserControl? [duplicate]

I try to use binding with an attached property. But can't get it working.
public class Attached
{
public static DependencyProperty TestProperty =
DependencyProperty.RegisterAttached("TestProperty", typeof(bool), typeof(Attached),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Inherits));
public static bool GetTest(DependencyObject obj)
{
return (bool)obj.GetValue(TestProperty);
}
public static void SetTest(DependencyObject obj, bool value)
{
obj.SetValue(TestProperty, value);
}
}
The XAML Code:
<Window ...>
<StackPanel local:Attached.Test="true" x:Name="f">
<CheckBox local:Attached.Test="true" IsChecked="{Binding (local:Attached.Test), Mode=TwoWay, RelativeSource={RelativeSource Self}}" />
<CheckBox local:Attached.Test="true" IsChecked="{Binding (local:Attached.Test), Mode=TwoWay}" />
</StackPanel>
</Window>
And the Binding Error:
System.Windows.Data Error: 40 : BindingExpression path error: '(local:Attached.Test)' property not found on 'object' ''StackPanel' (Name='f')'. BindingExpression:Path=(local:Attached.Test); DataItem='StackPanel' (Name='f'); target element is 'CheckBox' (Name=''); target property is 'IsChecked' (type 'Nullable`1')
Believe it or not, just add Path= and use parenthesis when binding to an attached property:
IsChecked="{Binding Path=(local:Attached.Test), Mode=TwoWay, RelativeSource={RelativeSource Self}}"
In addition, your call to RegisterAttached should pass in "Test" as the property name, not "TestProperty".
I'd have preferred to post this as a comment on Kent's answer but since I don't have enough rep to do so... just wanted to point out that as of WPF 4.5, adding Path= isn't necessary anymore. However the attached property name still needs to be wrapped with parentheses.
Putting a bracket works. I had to do automation id binding of a parent contentcontrol to a textblock in datatemplate. Automation Id is an attached property.
I put the property in brackets and binding worked.
AutomationProperties.AutomationId="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ContentControl},Path=(AutomationProperties.AutomationId)}"

How to bind to the Tag property of another element

What I want is to binding the Image source to the ImageData property of MyViewModel instance. For some reason, I cannot set MyWindow.DataContext as MyViewModel instance (it is leagacy code and it will cost too much to do this).
The code snippet is as below and there will be the following error
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=myButton'. BindingExpression:Path=Tag; DataItem=null; target element is 'Image' (Name=''); target property is 'Source' (type 'ImageSource')
// This is the view model class
class MyViewModel : INotifyPropertyChanged
{
public string Name {get ... ;set ... ; }
public string ImageData { ... }
}
// this is the window class
class MyWindow
{
private MyViewModel _ViewModel = new MyViewModel();
public MyViewModel ViewModel
{ get { return _ViewModel; } }
. . .
}
MyWindow.Xaml
<devComponent:ButtonDropDown x:Name="myButton"
Tooltip="{Binding RelativeSource={RelativeSource AncestorType={x:Type mf:MyWindow}}, Path=ViewModel.Name}"
Tag="{Binding RelativeSource={RelativeSource AncestorType={x:Type mf:MyWindow}}, Path=ViewModel.ImageData}"
...>
<devComponent:ButtonDropDown.Image>
<Image Source="{Binding Path=Tag, ElementName=myButton}" />
</devComponent:ButtonDropDown.Image>
</devComponent:ButtonDropDown>
I also tried to use below code but it still does not work because Image does not belong to the visual tree
Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='xxxxx', AncestorLevel='1''
<Button.Image>
<Image Source="{Binding RelativeSource={RelativeSource AncestorType={x:Type mf:MyWindow}}, Path=MyViewModel.ImageData}" />
</Button.Image>

How to bind List<string> to a DependencyProperty

I'm trying to learn about DependencyProperty. To do so I want to create a new UserControl which displays a list.
The location of this list must exist in the parent as a property. For this, I only have MainWindow, MainWindowViewModel (these are the parent) and the UserControl (the child) (which is currently using code behind).
In my MainWindow I have
<Grid>
<uc:RecentList MessageList="{Binding Messages}" />
</Grid>
And in the code behind
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
And the ViewModel
public MainWindowViewModel()
{
this.Messages = new ObservableCollection<string>();
this.Messages.Add("Item 1");
this.Messages.Add("Item 2");
this.T = "hi";
}
public ObservableCollection<string> Messages { get; set; }
In the UserControl I have
<Grid>
<ListView ItemsSource="{Binding MessageList}"></ListView>
<TextBlock Text="I'm such text to verify this control is showing" />
</Grid>
And the code behind is
public static readonly DependencyProperty MessageListProperty =
DependencyProperty.Register(
"MessageList", typeof(IEnumerable<string>), typeof(RecentList));
public IEnumerable<string> MessageList
{
get { return (IEnumerable<string>)GetValue(MessageListProperty); }
set { SetValue(MessageListProperty, value); }
}
The issue I have is the binding is not working. I can see this in the Output Window, with the Error:
Error 40 : BindingExpression path error: 'MessageList' property not found on 'object' ''MainWindowViewModel' (HashCode=26034861)'. BindingExpression:Path=MessageList; DataItem='MainWindowViewModel' (HashCode=26034861); target element is 'ListView' (Name=''); target property is 'ItemsSource' (type 'IEnumerable')
I understand the issue but I am confused by it. It's looking in the right place (in the MainWindowViewModel) but it is looking I don't understand why the UserControl is looking for the MessageList in the MainWindowViewModel. I guess it's because that is where I set the datacontext but, I also thought it that if I added this.DataContext = this; to the UserControl's constructor then it's wrong (I've tried it, it didn't work either).
Updating my UserControl to
<ListView ItemsSource="{Binding MessageList, RelativeSource={RelativeSource Mode=TemplatedParent}}"></ListView>
Helps in the sense I don't get the error message, but I also don't see the result.
This is what I think is happening when the application loads:
MainWindow loads
MainWindow then see's the UserControl and notes it requires a property.
Before WPF calls the UserControl constructor, it grabs the value of the property. It then initializes the component and automatically pushes the value to the UserControl's property
How can my UserControl use the Parents (MainWindow) property (Messages)
The Binding in the UserControl's XAML should have the UserControl instance as its source object, e.g. like this:
<ListView ItemsSource="{Binding MessageList,
RelativeSource={RelativeSource AncestorType=UserControl}}" />
Alternatively you could set x:Name on the UserControl and use an ElementName binding:
<UserControl ... x:Name="self">
...
<ListView ItemsSource="{Binding MessageList, ElementName=self}" />
...
</UserControl>
Besides that you should usually not set the DataContext of the UserControl to itself (like DataContext = this;) because that would effectively prevent inheriting the DataContext from the UserControl's parent element, which is necessary for an "external" binding to work, like:
<uc:RecentList MessageList="{Binding Messages}" />

DataGrid SelectedItem Being Bound to Wrong DataContext MVVM Pattern

I am trying to get the currently selected item of a datagrid that I have bound to a CollectionViewSource. However, it appears as if the SelectedItem property is not correctly binding to the property I have set in my ViewModel.
<Grid DataContext="{Binding CollectionView}">
<DataGrid ItemsSource="{Binding}" Margin="0,30,0,0" SelectedItem="{Binding SelectedRow}" />
</Grid>
When running the project, I see this error message in the output box of VS2010.
System.Windows.Data Error: 40 : BindingExpression path error: 'SelectedRow' property not found on 'object' ''BindingListCollectionView' (HashCode=56718381)'. BindingExpression:Path=SelectedRow; DataItem='BindingListCollectionView' (HashCode=56718381); target element is 'DataGrid' (Name=''); target property is 'SelectedItem' (type 'Object')
I understand that the SelectedItem property of the datagrid is trying to bind to the CollectionViewSource, but I am not quite sure how to tell the SelectedItem to bind to the SelectedRow property of my ViewModel. Any help would be appreciated. Also, if you need any more information on my setup, please feel free to ask.
Here is the property in my ViewModel, just in case it is needed:
public DataRow SelectedRow
{
get
{
return _selectedRow;
}
set
{
_selectedRow = value;
OnPropertyChanged("SelectedRow");
}
}
Change DataRow to whatever the actual type of object you are binding too is called.
public **Object each row represents in view model** SelectedRow
{
get
{
return _selectedRow;
}
set
{
_selectedRow = value;
OnPropertyChanged("SelectedRow");
}
}
I did some more digging, and was able to come up with a solution. Essentially, I needed to tell the SelectedItem property to look back at the DataContext of the MainWindow.
I changed the XAML to:
<Grid DataContext="{Binding CollectionView}">
<DataGrid ItemsSource="{Binding}" Margin="0,30,0,0" SelectedItem="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.SelectedRow}">
</DataGrid>
</Grid>
and then change the property within my ViewModel to a DataRowView instead of DataRow
public DataRowView SelectedRow
{
get
{
return _selectedRow;
}
set
{
_selectedRow = value;
OnPropertyChanged("SelectedRow");
}
}
Thanks everyone!
You have SelectedItem in your binding, and the name of your property is SelectedRow - make sure these are the same.
SelectedRow is not a property of CollectionView. I assume both are properties of your ViewModel:
<Grid DataContext="{Binding}">
<DataGrid ItemsSource="{Binding CollectionView}"
SelectedItem="{Binding SelectedRow}" />
</Grid>

Databind radiobutton group to property

I have a radiobutton group:
<TextBlock Height="24" Text="Update Interval (min):"/>
<RadioButton x:Name="radioButtonTimerNone" IsChecked="{Binding UpdateInterval, Converter={StaticResource updateIntervalToCheckedConverter}, Mode=TwoWay}"} Content="None" />
<RadioButton x:Name="radioButtonTimerOne" IsChecked="{Binding UpdateInterval, Converter={StaticResource updateIntervalToCheckedConverter}, Mode=TwoWay}"
Content="1" />
<RadioButton x:Name="radioButtonTimerFive" IsChecked="{Binding UpdateInterval, Converter={StaticResource updateIntervalToCheckedConverter}, Mode=TwoWay}"
Content="5" />
And a property:
public int UpdateInterval {
get { return _updateInterval; }
set { _updateInterval = value;
onPropertyChanged("UpdateInterval");
}
}
How do I bind the radiobuttons to the property, so radioButtonTimerNone is checked when UpdateInterval is 0, radioButtonTimerOne is checked when UpdateInterval is 1, etc.
I have tried to create a converter, but it doesn't identify which rb is being set:
[ValueConversion(typeof(RadioButton), typeof(bool))]
class UpdateIntervalToCheckedConverter : System.Windows.Data.IValueConverter
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
I expected 'value' to be a radiobutton, but it appears to be the value of UpdateInterval.
Thanks for any hints...
If you are using MVVM and are bound to a ViewModel (I would guess that you are), I usually consider my ViewModel to be a big ValueConverter. Why not put that logic into properties for each?
Here's an example of one of them:
public bool Timer5Enabled
{
get { return UpdateInterval == 5; }
}
And then you'd just bind to that:
<RadioButton
x:Name="radioButtonTimerOne"
IsChecked="{Binding Timer5Enabled, Mode=OneWay}"
Content="1" />
The only thing you'd need to change would be to tie your interval update logic to raise OnChanged for your dependent properties:
public int UpdateInterval {
get { return _updateInterval; }
set { _updateInterval = value;
onPropertyChanged("UpdateInterval");
onPropertyChanged("Timer5Enabled");
onPropertyChanged("...");
}
}
ValueConverters are good to avoid if you can.
Your value converter doesn't get told which RadioButton changed the value - all the binding knows is that the "IsChecked" property was changed, so the new value for IsChecked is the only thing it can tell the converter.
The first thing that springs to my mind is to supply a converter parameter with each of your bindings:
<RadioButton
x:Name="radioButtonTimerNone"
IsChecked="{Binding UpdateInterval, Converter={StaticResource updateIntervalToCheckedConverter}, ConverterParameter=0, Mode=TwoWay}"
Content="None" />
<RadioButton
x:Name="radioButtonTimerOne"
IsChecked="{Binding UpdateInterval, Converter={StaticResource updateIntervalToCheckedConverter}, ConverterParameter=1, Mode=TwoWay}"
Content="1" />
<RadioButton
x:Name="radioButtonTimerFive"
IsChecked="{Binding UpdateInterval, Converter={StaticResource updateIntervalToCheckedConverter}, ConverterParameter=5, Mode=TwoWay}"
Content="5" />
So now the "parameter" parameter on the "Convert" method will have the value "0", "1" or "5" depending on which RadioButton was checked. I think, though I'm not certain, that the parameter will be of type string, so you may have to take that into account when interrogating the value.
You could probably consider having a List<...> with different intervals. The list type should preferably be some custom type (e.g. UpdateInterval) or a KeyValuePair<K,V> to decouple what is shown to the user from what is an actual value.
Then you could have a ListBox bound to this list, and each ListBoxItem templated to show a RadioButton. Then, in the template, you bind radio button's IsChecked to ListBoxItem.IsSelected.
The final touch is just to bind your property to the ListBox's SelectedItem or SelectedValue.
This has been a very annoying problem for me.
The WPF RadioButton class has a bug that removes the binding for the IsChecked property when you assign multiple RadioButtons to the same GroupName. People have given many work arounds, but when I had to visit this again I came up with one that doesn't make me queasy.
So what you have to do is subclass the RadioButton class with this:
public class RadioButton: System.Windows.Controls.RadioButton
{
protected override void OnClick()
{
base.OnClick();
SetValue(CurrentValueProperty,CheckedValue);
}
public int CurrentValue
{
get { return (int)GetValue(CurrentValueProperty); }
set { SetValue(CurrentValueProperty, value); }
}
// Using a DependencyProperty as the backing store for CurrentValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CurrentValueProperty =
DependencyProperty.Register("CurrentValue", typeof(int), typeof(RadioButton), new FrameworkPropertyMetadata(0,FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, CurrentValue_Changed));
public static void CurrentValue_Changed(object sender, DependencyPropertyChangedEventArgs e)
{
((RadioButton)sender).IsChecked = ((RadioButton)sender).CurrentValue == ((RadioButton)sender).CheckedValue;
}
public int CheckedValue
{
get { return (int)GetValue(CheckedValueProperty); }
set { SetValue(CheckedValueProperty, value); }
}
// Using a DependencyProperty as the backing store for CheckedValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CheckedValueProperty =
DependencyProperty.Register("CheckedValue", typeof(int), typeof(RadioButton), new UIPropertyMetadata(0));
}
All this does is add two dependency properties so you can do an equality comparison.
So in XAML an example would be this:
<UserControl x:Class="MyClass"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:MVVMLibrary;assembly=MVVMLibrary"
Height="Auto" Width="Auto">
<c:RadioButton CurrentValue="{Binding MyValue}" CheckedValue="1" GroupName="SearchType" >Value 1</c:RadioButton>
<c:RadioButton Grid.Column="1" CurrentValue="{Binding MyValue}" CheckedValue="2" GroupName="SearchType" >Value 2</c:RadioButton>
</UserControl>
So If you notice all you do is
1) Bind to the CurrentValue property
2) Set the CheckedValue property to the value that would make the RadioButton checked
3) Set the RadioButtons to the same GroupName
If you notice i made CurrentValue and CheckedValue int type. The reason I did this is so that you could actually bind CurrentValue to an enumeration. I think this is awesome, but that's just my opinion which probably doesn't count for much. :)
Hope this helps somebody.
One possibility is to implement an IValueConverter and configure it passing the expected enumeration value for every RadioButton you need to bind, as I described here in my blog.

Resources