Issue with binding custom DependencyProperty to ListView.SelectedItem - c#

I'm having an issue with a binding that I'm trying to implement. It will update the DP once, but after that, it's never updated again.
In XAML I have two controls binding to a listview.selected item.
<controls:MapControl DataContext="{Binding ElementName=availableMapsListView, Path=SelectedItem}" MapData="{Binding .}">
and
<TextBlock DataContext="{Binding ElementName=availableMapsListView, Path=SelectedItem}" Text="{Binding Name}" />
The textblock update as expected with each change of the listview's selected item.
My custom control creates the dependency property like so:
public class MapControl : UserControl
{
public MapData MapData
{
get { return (MapData)GetValue(MapDataProperty); }
set { SetValue(MapDataProperty, value); }
}
public static readonly DependencyProperty MapDataProperty =
DependencyProperty.Register("MapData", typeof(MapData), typeof(MapControl),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(OnMapDataPropertyChanged),
new CoerceValueCallback(OnMapCoerceValue)
)
);
private static void OnMapDataPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != null)
{
((MapControl)source).MapData = (MapData)e.NewValue;
}
}
private static object OnMapCoerceValue(DependencyObject dpo, Object obj)
{
return obj;
}
...
}
I'm pretty much at my wits end and not sure what I should do from here. Any help is greatly appreciated.

Not sure exactly what you're trying to achieve or why your code appears so convoluted. If you explain more someone may be able to provide you with a much simpler solution.
That said, by the sounds of it the problem is simply that you're overwriting the binding with a local value. This looks like the culprit:
((MapControl)source).MapData = (MapData)e.NewValue;
When you do this, the MapControl.MapData property will no longer be bound to '.' Instead, it will take on whatever value you've assigned. So your MapControl.DataContext property is likely perfectly correct, but it's not being transferred to the MapData property because you've destroyed the binding.

I had the same error last week. My solution was simple : When you explicitly define a DependencyProperty you must also explicitly define the mode to TwoWay.
<TextBlock DataContext="{Binding ElementName=availableMapsListView,
Path=SelectedItem}"
Text="{Binding Name, Mode=TwoWay}" />

Related

Bind textblock visibility with custom property of grid

Hello,
After reading lots of topics about visibility binding for hours, I'm asking here because I don't manage to make my case works.
I have a grid with a custom attached property (type System.Windows.Visibily) which I want to use to display (or not) a textblock inside the grid (by binding). Also I want to change the visibility everytime the custom attached property change.
What I have done so far :
CustomProperties class :
public static class CustomProperties
{
public static readonly DependencyProperty starVisibilityProperty =
DependencyProperty.RegisterAttached("starVisibility",
typeof(System.Windows.Visibility), typeof(CustomProperties),
new FrameworkPropertyMetadata(null));
public static System.Windows.Visibility GetStarVisibility(UIElement element)
{
if (element == null)
throw new ArgumentNullException("element");
return (System.Windows.Visibility)element.GetValue(starVisibilityProperty);
}
public static void SetStarVisibility(UIElement element, System.Windows.Visibility value)
{
if (element == null)
throw new ArgumentNullException("element");
element.SetValue(starVisibilityProperty, value);
}
}
Then here is my xaml :
<Grid Name="server1State" Grid.Row="1" local:CustomProperties.StarVisibility="Hidden">
<TextBlock Name="server1Star" Text="" FontFamily="{StaticResource fa-solid}" FontSize="30" Margin="10" Foreground="#375D81" Visibility="{Binding ElementName=server1State, Path=server1State.(local:CustomProperties.starVisibility)}"/>
</Grid>
But when I run my app, the textblock is absolutely not hidden, this is visible, and never change. I have tried lots of things with Path and also INotifyPropertyChanged but as I am working with static custom attached property, I didn't manage to make it works.
Maybe some of you could help me, thanks.
Your Binding.Path on the TextBlock is wrong.
Since I've read from your comment, that you prefer to use a boolean property, I'll show how to convert the bool value to a Visibility enumeration value using the library's BooleanToVisibilityConverter.
I think you may already got it, but then got confused due to your wrong Binding.Path:
CustomProperties.cs
public class CustomProperties : DependencyObject
{
#region IsStarVisibile attached property
public static readonly DependencyProperty IsStarVisibileProperty = DependencyProperty.RegisterAttached(
"IsStarVisibile",
typeof(bool),
typeof(CustomProperties),
new PropertyMetadata(default(bool)));
public static void SetIsStarVisibile(DependencyObject attachingElement, bool value) => attachingElement.SetValue(CustomProperties.IsStarVisibileProperty, value);
public static bool GetIsStarVisibile(DependencyObject attachingElement) => (bool)attachingElement.GetValue(CustomProperties.IsStarVisibileProperty);
#endregion
}
MainWindow.xaml
<Window>
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</Window.Resources>
<Grid Name="Server1StateGrid"
CustomProperties.IsStarVisibile="False">
<TextBlock Text=""
Visibility="{Binding ElementName=Server1StateGrid,
Path=(CustomProperties.IsStarVisibile),
Converter={StaticResource BooleanToVisibilityConverter}}" />
</Grid>
</Window>

changing the bound object in a datatemplate

Data templates are great, but I'm having a problem with binding in a particular situation. I have a class, Value, that has various descendants like StringValue, DateValue, etc. These Values show up in a Listbox. This template works fine, binding to a specific property of StringValue:
<DataTemplate DataType="{x:Type values:StringValue}">
<TextBox Margin="0.5"
Text="{Binding Path=Native}" />
</DataTemplate>
However, when I bind to an object itself, instead of a specific property, the changes don't update the object, as in this template:
<DataTemplate DataType="{x:Type values:LookupValue}">
<qp:IncrementalLookupBox SelectedValue="{Binding Path=., Mode=TwoWay}"
LookupProvider="{Binding ElementName=EditWindow, Path=ViewModel.LookupProvider}">
</qp:IncrementalLookupBox>
</DataTemplate>
IncrementalLookupBox is a UserControl that ultimately allows a user to select a LookupValue, which should replace the item bound in the template. If this was bound to a simple type like an int or string, the binding would replace the object, so I'm not sure what the difference is with a more complex object. I know that the IncrementalLookBox is working, because binding some textboxes to the properties of SelectedValue (which is a dependency property) shows the correctly selected LookupValue.
In case it makes the situation more clear, here is the implementation of SelectedValue:
public LookupValue SelectedValue
{
get { return (LookupValue)GetValue(SelectedValueProperty); }
set { SetValue(SelectedValueProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedValueProperty =
DependencyProperty.Register("SelectedValue", typeof(LookupValue), typeof(IncrementalLookupBox), new PropertyMetadata(OnSelectedValuePropertyChanged));
private static void OnSelectedValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var obj = d as IncrementalLookupBox;
obj.OnSelectedValuePropertyChanged(e);
}
private void OnSelectedValuePropertyChanged(DependencyPropertyChangedEventArgs e)
{
CheckForSelectedValueInLookups();
}
If all else fails consider using a ValueConverter to get the value you require.
Edit: this does not work. See link in comments below.
Make sure your class implements INotifyPropertyChanged and raise PropertyChanaged here:
private void OnSelectedValuePropertyChanged(DependencyPropertyChangedEventArgs e)
{
CheckForSelectedValueInLookups();
// RaisePropertyChanged();
}
My issue is the same as described here:
WPF TwoWay Binding of ListBox using DataTemplate
Apparently if I don't write enough text here, my answer will be converted to a comment and not close out the question. So, to summarize the issue, a two-way Binding=. in a datatemplate used in a ListBox (or any ItemsControl I image) won't work, because it is not the object itself being bound, but the ListBoxItem that contains it.

UserControl depends on TreeView's (WPF) SelectedItem

I have two user controls, one contains a TreeView, one contains a ListView.
The TreeView has an itemsource and hierarchical data templates that fill the nodes and leafes (node=TvShow, leaf=Season).
The ListView should show the children of the selected TreeView item (thus, the selected season): the episodes of that season.
This worked fine when I had both the TreeView and the Listview defined in the same window, I could use something like this:
<ListView
x:Name="_listViewEpisodes"
Grid.Column="2"
ItemsSource="{Binding ElementName=_tvShowsTreeView, Path=SelectedItem.Episodes}">
How can I achieve this, when both controls are defined in separate user controls? (because in the context of one user control, I miss the context of the other user control)
This seems something pretty basic and I am getting frustrated that I can't figure it out by myself. I refuse to solve this with code-behind, I have a very clean MVVM project so far and I would like to keep it that way.
Hope that somebody can give me some advise!
First of all you have to created the SelectedValue proeprty in your ViewModel and bind the TreeView.SelectedItem property to it. Since the SelectedItem property is read-only I suggest you to create a helper to create OneWayToSource-like binding. The code should be like the following:
public class BindingWrapper {
public static object GetSource(DependencyObject obj) { return (object)obj.GetValue(SourceProperty); }
public static void SetSource(DependencyObject obj, object value) { obj.SetValue(SourceProperty, value); }
public static object GetTarget(DependencyObject obj) { return (object)obj.GetValue(TargetProperty); }
public static void SetTarget(DependencyObject obj, object value) { obj.SetValue(TargetProperty, value); }
public static readonly DependencyProperty TargetProperty = DependencyProperty.RegisterAttached("Target", typeof(object), typeof(BindingWrapper), new PropertyMetadata(null));
public static readonly DependencyProperty SourceProperty = DependencyProperty.RegisterAttached("Source", typeof(object), typeof(BindingWrapper), new PropertyMetadata(null, OnSourceChanged));
static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
SetTarget(d, e.NewValue);
}
}
The idea is simple: you have two attached properties, the Source and the Target. When the first one changes the PropertyChangedCallback is called and you simply setting the NewValue as the Target property value. In my opinion this scenario is helpful in a lot of cases when you need to bind the read-only property in XAML (especially in control templates).
I've created a simple model to demonstrate how to use this helper:
public class ViewModel : INotifyPropertyChanged {
public ViewModel() {
this.values = new ObservableCollection<string>()
{
"first",
"second",
"third"
};
}
ObservableCollection<string> values;
string selectedValue;
public ObservableCollection<string> Values { get { return values; } }
public string SelectedValue {
get { return selectedValue; }
set {
if (Equals(selectedValue, values))
return;
selectedValue = value;
if (PropertyChanged == null)
return;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedValue"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
So, we have data source, selected value and we'll bind it like this:
<StackPanel>
<TreeView ItemsSource="{Binding Values}"
local:BindingWrapper.Source="{Binding SelectedItem, RelativeSource={RelativeSource Self}, Mode=OneWay}"
local:BindingWrapper.Target="{Binding SelectedValue, Mode=OneWayToSource}"
>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Header" Value="{Binding}"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
<TextBlock Text="{Binding SelectedValue}"/>
</StackPanel>
In the TreeView bound to the ItemsSource from the ViewModel I've created two bindings so they are changing the SelectedValue property in your ViewModel. TextBlock in the end of the sample is used just to show that this approach works.
About the very clean MVVM - I think that it is not the same as the "no code-behind". In my sample the ViewModel still doesn't know anything about your view and if you'll use another control to show your data e.g. ListBox you will be able to use the simple two-way binding and the "BindingWrapper" helper will not make your code unreadable or unportable or anything else.
Create a SelectedSeason property in your ViewModel and bind the ListView's ItemsSource to SelectedSeason.Episodes.
In a perfect world, you could now use a Two-Way binding in the TreeView to automatically update this property when the SelectedItem changes. However, the TreeView's SelectedItem property is readonly and cannot be bound. You can use just a little bit of code-behind and create an event handler for the SelectionChanged event of the TreeView to update your ViewModel's SelectedSeason there. IMHO this doesn't violate the the MVVM principles.
If you want a pure XAML solution, that a look at this answer.

TemplateBinding not working for textbox text

I have a custom control called EnhancedTextBox which is a UserControl that has a TextBox and a Button. To the consumer I want it to mostly look like a TextBox, so I did the following:
<UserControl.Template>
<ControlTemplate TargetType="textBoxes:EnhancedTextBox">
...
<TextBox Text="{TemplateBinding Text}"...
And in EnhancedTextBox I have
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof (String), typeof (EnhancedTextBox));
public String Text
{
get { return (String) GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
Yet, when I use it as the following:
<EnhancedTextBox Text="{Binding MyText, Mode=TwoWay, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}}" />
Then, MyText is never updated, as well as I inspect EnhancedTextBox.Text and it is null. What am I missing? I have been staring at this for a bit and can't figure out what is wrong. I even thought it might be the fact that I was using the same name, so create a property called Text1 which did not work....
Also of note, if I use a regular TextBox, then this all works. So, I am fairly certain the problem is with the EnhancedTextBox itself
I figured it out after reading this MSDN about TemplateBinding. Specifically,
A TemplateBinding is an optimized form of a Binding for template scenarios, analogous to a Binding constructed with {Binding RelativeSource={RelativeSource TemplatedParent}}.
So, I decided to do this explicitly...which would allow me to set the UpdateSourceTrigger (still not sure why it doesn't default to PropertyChanged)
<TextBox Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Text, UpdateSourceTrigger=PropertyChanged}"....
And, now it is working. TemplateBinding does not even expose these properties....again, not sure why
You are missing the CallBack when you register the property.
Here's a sample code.
public bool IsSelected
{
get { return (bool)GetValue(IsSelectedProperty); }
set { SetValue(IsSelectedProperty, value); }
}
public void IsSelectedChangedCallback()
{
//actions when property changed
}
private static void OnSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
userControl.IsSelectedChangedCallback();
}
public static readonly DependencyProperty IsSelectedProperty = DependencyProperty.Register("IsSelected", typeof(bool), typeof(MyUserControl), new PropertyMetadata(new PropertyChangedCallback(OnSelectedChanged)));

How to bind a ConvertorParameter

I am trying to bind the value of a ConverterParameter. Currently finding it too tricky...
Codebehind
public static readonly DependencyProperty RecognitionProperty = DependencyProperty.Register("RecognitionToEdit", typeof(Recognition), typeof(RecognitionInstancesWindow), null);
public Recognition Recognition
{
get { return (Recognition)GetValue(RecognitionProperty); }
set { SetValue(RecognitionProperty, value); }
}
XAML of a TextBox, which forms part of a datatemplate for a coverflow type control.
<TextBlock HorizontalAlignment="Left" Margin="2,0,0,0" Text="{Binding Converter={StaticResource DateConverter}, Path=Date, ConverterParameter={Binding Recognition, Path=Frequency}}" />
Can anyone see where I'm going wrong please?
Unfortunately it is not possible, that's because for property to be bindable it should be dependency, and the object should be derived from DependencyObject. Binding is not derived from DependencyObject, so it is impossible, you should look another ways to do that
One way to do that is to create a class in static resource, and pass that class to your converter like this
<namespace:MyClass x:Key="MyClass">
<Binding ... ConvertParameter={StaticResource MyClass}/>
from MyClass you can return anything you want ;)
this post can be helpful

Categories

Resources