wpf binding from a FindAncestor to Dependency Property of custom control - wpf

I've got a custom WPF control with a DependencyProperty MyString
I'm using the control within an ItemsControl on my View and want to fish a value out from the ViewModel.
As the DataContext of the control becomes each Item in the ItemsSource of the ItemsControl I thought I'd just be able to use FindAncestor but it dosnt seem to work ... can anyone see where I'm going wrong please?
Heres the XAML on the View ...
<Grid>
<ItemsControl ItemsSource="{Binding MyItems}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Name="myStack">
<ImportExceptions:ControlStrip MyString="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemsControl}}, Path=DataContext.MyStringOnViewModel}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
and heres the code behind my custom control where I've set up my dependency property ...
public partial class ControlStrip
{
public ControlStrip()
{
InitializeComponent();
}
public string MyString
{
get
{
return GetValue(MyStringProperty).ToString();
}
set
{
SetValue(MyStringProperty, value);
}
}
public static readonly DependencyProperty MyStringProperty =
DependencyProperty.RegisterAttached("MyString", typeof (string), typeof (ControlStrip));
}

The DataContext of the control doesn't change - the DataContext for the ImportExceptions:ControlStrip will be (unless explicitly specified) the next DataContext available as its goes 'up' the visual tree...
I infer from your code that you have set the DataContext of the View to a ViewModel with properties 'MyItems' and 'MyStringOnViewModel' - you should be able to simply bind the MyString property directly to the ViewModel, like
<ImportExceptions:ControlStrip MyString="{Binding Path=MyStringOnViewModel}" />

Your code looks fine. Probably you have made an error in the DataContext reference. In all likeliness the DataContext of the the ItemsControl already is MyStringOnViewModel. So, omit the .MystringOnViewModel after the DataContext in the Path attribute. If not can you give some more code, ore post a simplification of it that mimicks how the DataCon,text(s) is/are set?

Related

Understanding dependancy objects

I'm totally lost with dependancy objects and binding. I often get things working without understanding why and how, this question is about knowing what should be happening.
I have a tiny user control with the following XAML
<Grid>
<Image Source="{Binding Icon}"></Image>
<TextBlock Text="{Binding Title}"></TextBlock>
</Grid>
My code behind has the following
public static readonly DependencyProperty IconProperty =
DependencyProperty.Register("Icon", typeof(Image), typeof(MenuItem));
public Image Icon
{
get { return (Image)GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(String), typeof(MenuItem));
public string Title
{
get { return (string)GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}
My MainWindow is empty, other than a reference to this control and to the ResourceDictionary. In the MainWindow code behind, I set the DataContext in the constructor.
<Window x:Class="AppUi.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:AppUi.Control"
Title="">
//set up to Resource Dictionary - all binding and styling works fine :)
<loc:MenuItem Icon="{Binding MailIcon}" Title="{Binding MailTitle}"></loc:MenuItem>
In the ModelView for the MainWindow, I have the following 2 properties
private Image_mailIcon;
public Image MailIcon{
//inotifyproperty implementation
}
private string _mailTitle;
public string MailTitle{
//inotifyproperty implementation
}
My question is, in the UserControl, how do I do the binding? Since it's a user control within a MainWindow, and the MainWindow already has a datacontext, I think the UserControl will inherit the DataContext from the parent (From what I have read).
So, in my UserControl XAML, should I be binding to the MainWindow's Code Behind properties OR to the ViewModel properties?
In other words, should my UserControl be
<Grid>
<Image Source="{Binding MailIcon}"></Image>
<TextBlock Text="{Binding MailTitle}"></TextBlock>
</Grid>
OR
<Grid>
<Image Source="{Binding Icon}"></Image>
<TextBlock Text="{Binding Title}"></TextBlock>
</Grid>
Or, because I'm using a DataContext and the UserControl inherits, do I even need the Dependancy Properties at all?
You normally don't want to overwrite DataContext passed through visual tree so you can use either ElementName or RelativeSource binding inside UserControl to change binding context. The easiest way to achive this is give UserControl some name and use it ElementName binding
<UserControl ... x:Name="myUserControl">
<!-- ... -->
<Grid>
<Image Source="{Binding Icon, ElementName=myUserControl}"/>
<TextBlock Text="{Binding Title, ElementName=myUserControl}"/>
</Grid>
<!-- ... -->
</UserControl>
This way binding is DataContext independent. You can also create UserControl with assumption it will always work with only specific type of DataContext and then you just use Path from that view model type but then DataContext of that UserControl must always be of the view model it's designed for (mostly inherited through visual tree)
<UserControl ...>
<!-- ... -->
<Grid>
<Image Source="{Binding MailIcon}"/>
<TextBlock Text="{Binding MailTitle}"/>
</Grid>
<!-- ... -->
</UserControl>
I would also change type of Icon property from Image to ImageSource for example. You already have Image control inside your UserControl and you just want to bind its Source
in the UserControl, how do I do the binding? ... the UserControl will inherit the DataContext from the parent
That is correct, the UserControl will inherit the DataContext from the parent Window. Therefore you can data bind from the UserControl directly to the parent Window.DataContext. Please note that you would bind to whatever object has been set as the DataContext, regardless of whether that was the code behind or a separate view model class.
However, you don't have to data bind to the parent's DataContext object in this situation... you have other options. You could data bind to your own UserControl DependencyPropertys using a RelativeSource Binding like this:
<TextBlock Text="{Binding Title, RelativeSource={RelativeSource
AncestorType={x:Type YourPrefix:YourUserControl}}}" />
You could also name your UserControl and reference its properties like this:
<TextBlock Text="{Binding Title, ElementName=YourUserControlName}" />
While this example seems to be more concise, don't overlook the first example, as RelativeSource is a useful and powerful friend to have.
should I be binding to the MainWindow's Code Behind properties OR to the ViewModel properties?
That's your choice... what do you want or need to data bind to? you just need to know that a direct data binding will use the auto set DataContext value, so if you don't want to use that, then you can just specify a different data source for the Binding as shown above.
Finally, regarding the need to use DependencyPropertys... you only need to declare them if you are developing a UserControl that needs to provide data binding abilities.

Binding TwoWay to SelectedItem: "Wrong way" synchronization on initialization

I am trying to bind a property of my DataContext to the SelectedItem on a ComboBox like this:
<ComboBox x:Name="ElementSelector"
ItemsSource="{Binding Source={StaticResource Elements}}"
DisplayMemberPath="ElementName"
SelectedItem="{Binding ValueElement, Mode=TwoWay}">
where the Elements resource is a CollectionViewSource (don't know, whether this matters).
When everything is initialized, the property ValueElement of the DataContext is set to the first item in the CollectionViewSource. What I want, is to initialize it the other way around: I would like to set SelectedItem of the ComboBox to the value of the property or null if no matching item is contained.
How can this be done?
EDIT - Additional information:
The ComboBox is part of a DataTemplate:
<DataTemplate x:Key="ReferenceTemplate"
DataType="viewModels:ElementMetaReferenceViewModel">
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<ResourceDictionary>
<views:ElementsForReferenceViewSource x:Key="Elements"
Source="{Binding DataContext.CurrentProject.Elements, ElementName=Root}"
ReferenceToFilterFor="{Binding}"/>
</ResourceDictionary>
</StackPanel.Resources>
<TextBlock Text="{Binding PropertyName}"/>
<ComboBox x:Name="ElementSelector"
ItemsSource="{Binding Source={StaticResource Elements}}"
DisplayMemberPath="ElementName"
SelectedItem=""{Binding ValueElement, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
The ElementsForReferenceViewSource simply derives from CollectionViewSource and implements an additional DependencyProperty which is used for filtering.
The DataContext of the items in the CollectionViewSource look like this:
public class ElementMetaReferenceViewModel : ViewModelBase<ElementMetaReference, ElementMetaReferenceContext>
{
...
private ElementMetaViewModel _valueElement;
public ElementMetaViewModel ValueElement
{
get { return _valueElement; }
set
{
if (value == null) return;
_valueElement = value;
Model.TargetElement = value.Model;
}
}
...
}
For people encountering the same issue
The above code works as expected. The solution was getting the stuff behind the scenes right. Make sure, that the instance of the ViewModel which is the value of the property you want to bind to is definitely contained in the CollectionViewSource.
In my case the issue was deserializing an object tree incorrectly, so objects were instantiated twice. Then for each object a distinct ViewModel was initialized and then obviously the value of the property was not contained in the list.
Remark
To check whether this is an issue in your case, you can try the following:
Override the ToString() methods of the ViewModels displayed in the ComboBox like this:
public override string ToString()
{
return "VM"+ Model.GetHashCode().ToString();
}
Then you can easily compare the items in the source collection with the value on your property. Not the most professional way, but it did the job for me.

How to bind a property in itemtemplate to a proptery in itemsource?

Consider this container:
public class ItemInfo : DependencyObject
{
public string Name { get; set; }
public ObservableCollection<SomeDataItem> DataValues { get; set; }
...
Dependency object registration and event handling
...
}
public class MyItemSource : ObservableCollection<ItemInfo>
{
...
}
Now, I wish to display this data in a listview where the control that displays the item is custom. For that, I'd set the MyItemSource to listview's ItemSource and define a ItemTemplate. However, it seems that I have no access to ItemInfo in the ItemTemplate. This is my XAML:
<Grid>
<ListBox ItemsSource="{StaticResource MyStaticDataSource}"
Grid.IsSharedSizeScope="True">
<ListBox.ItemTemplate>
<DataTemplate>
<local:ItemInfoUserControl x:Name="itemInfoUserControl"
Name = "{Binding Name}" <--- this doesn't work
Data = "{Binding DataValues}" <--- this doesn't work
Width="300" Height="200"
Grid.Row="1">
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Instead of binding to ItemSource's ItemInfo, it binds to the ItemInfoUserControl properties, which is not what I wish it to do. Is there a way to bind properties in itemtemplate to a property in itemsource ? Or is there an alternative approach to what i'm ultimately trying to accomplish ?
Thanks!
Well, first of all you need to use proper binding syntax:
Name = "{Binding Name}"
and
Data = "{Binding DataValues}"
Instead of just "Name = "Binding Name"" and "Data = "Binding DataValues"". Note the addition of "{" and "}" around your binding expression.
This might be enough to solve your problem, as long as Name and DataValues are DependencyProperties in ItemInfoUserControl. If not, you'll need to implement them as DependencyProperties in order to be able to bind to them in XAML. See here for a good MSDN article on defining custom dependency properties.
Edit: Also, just noticed -- you're setting both x:Name and Name. From this article on MSDN:
If Name is available as a property on the class, Name and x:Name can be used interchangeably as attributes, but a parse exception will result if both are specified on the same element. If the XAML is markup compiled, the exception will occur on the markup compile, otherwise it occurs on load.
Remove x:Name="itemInfoUserControl" and see if that helps.

Can I apply a ContextMenu to a ContextMenuViewModel using a DataTemplate?

I have a ViewModel (AbstractContextMenu) that represents my context menu (IContextMenu), and I bind a real ContextMenu to it with a DataTemplate:
<DataTemplate DataType="{x:Type local:AbstractContextMenu}">
<ContextMenu x:Name="contextMenu"
ItemsSource="{Binding Path=(local:IContextMenu.Items)}"
IsEnabled="{Binding Path=(local:IContextMenu.IsEnabled)}"/>
</DataTemplate>
Then I have a dummy ConcreteContextMenu for testing that just inherits from AbstractContextMenu. AbstractContextMenu just implements this interface:
public interface IContextMenu : IExtension
{
IEnumerable<IMenuItem> Items { get; set; }
bool IsEnabled { get; set; }
}
I'm using it as a property of another ViewModel object:
public IContextMenu ContextMenu
{
get
{
return m_ContextMenu;
}
protected set
{
if (m_ContextMenu != value)
{
m_ContextMenu = value;
NotifyPropertyChanged(m_ContextMenuArgs);
}
}
}
private IContextMenu m_ContextMenu = new ConcreteContextMenu();
static readonly PropertyChangedEventArgs m_ContextMenuArgs =
NotifyPropertyChangedHelper.CreateArgs<AbstractSolutionItem>(o => o.ContextMenu);
Then I bind a StackPanel to that ViewModel and bind the ContextMenu property on the StackPanel to the ContextMenu property of the ViewModel:
<StackPanel Orientation="Horizontal"
ContextMenu="{Binding Path=(local:AbstractSolutionItem.ContextMenu)}"
ContextMenuOpening="stackPanel_ContextMenuOpening">
<!-- stuff goes in here -->
</StackPanel>
When I run this, the ContextMenuOpening event on the StackPanel is fired, but the ContextMenu is never displayed. I'm not sure if I can even do this (apply a ContextMenu to a ContextMenu ViewModel using a DataTemplate). Anyone know?
What is the type of AbstractSolutionItem.ContextMenu? If it corresponds to the ContextMenu property in your question, then the problem could be that the type is wrong. The ContextMenu property of FrameworkElement is expecting an actual ContextMenu, not an IContextMenu. Try checking the output window while debugging your app - you might get an error message stating that this is the problem.
Instead of using a DataTemplate to define your ContextMenu, just put the contents of the template StackPanel.ContextMenu:
<StackPanel Orientation="Horizontal"
ContextMenu="{Binding Path=(local:AbstractSolutionItem.ContextMenu)}"
ContextMenuOpening="stackPanel_ContextMenuOpening">
<StackPanel.ContextMenu DataContext="{Binding Path=(local:AbstractSolutionItem.ContextMenu)}">
<ContextMenu x:Name="contextMenu"
ItemsSource="{Binding Path=Items}"
IsEnabled="{Binding Path=IsEnabled}"/>
</StackPanel.ContextMenu>
<!-- stuff goes in here -->
</StackPanel>
That should get you most of the way there. However, there is still a problem since the ContextMenu does not know how to create a MenuItem from an IMenuItem. To solve this, create an ItemTemplate for the ContextMenu, which binds members of IMenuItem to `MenuItem.
Could you shed some light on the syntax used in the ItemsSource property in the DataTemplate ? Using parentheses usually means an attached property. And Items does not seem to be an attached property defined by IContextMenu (as an interface cannot define such a property).
The DataTemplate is linked to an object of type AbstractContextMenu which has a property called Items. So, the DataTemplate could simply reference it like this:
<DataTemplate DataType="{x:Type local:AbstractContextMenu}">
<ContextMenu x:Name="contextMenu"
ItemsSource="{Binding Path=Items)}"
IsEnabled="{Binding Path=IsEnabled}"/>
</DataTemplate>
If the AbstractSolutionItem class is the VM of the StackPanel, you could bind it like this:
<StackPanel Orientation="Horizontal"
ContextMenu="{Binding Path=ContextMenu}"
ContextMenuOpening="stackPanel_ContextMenuOpening">
<!-- stuff goes in here -->
</StackPanel>
Of course, the DataTemplate must be "accessible" from the StackPanel.
Bind the ContextMenu property of your view (StackPanel in this scenario) to the ContextMenu property of your ViewModel and provide a IValueConverter to the binding that will create the ContextMenu object and set the IContextMenu to it's DataContext.

WPF Databinding CheckBox.IsChecked

How would I bind the IsChecked member of a CheckBox to a member variable in my form?
(I realize I can access it directly, but I am trying to learn about databinding and WPF)
Below is my failed attempt to get this working.
XAML:
<Window x:Class="MyProject.Form1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Title" Height="386" Width="563" WindowStyle="SingleBorderWindow">
<Grid>
<CheckBox Name="checkBoxShowPending"
TabIndex="2" Margin="0,12,30,0"
Checked="checkBoxShowPending_CheckedChanged"
Height="17" Width="92"
VerticalAlignment="Top" HorizontalAlignment="Right"
Content="Show Pending" IsChecked="{Binding ShowPending}">
</CheckBox>
</Grid>
</Window>
Code:
namespace MyProject
{
public partial class Form1 : Window
{
private ListViewColumnSorter lvwColumnSorter;
public bool? ShowPending
{
get { return this.showPending; }
set { this.showPending = value; }
}
private bool showPending = false;
private void checkBoxShowPending_CheckedChanged(object sender, EventArgs e)
{
//checking showPending.Value here. It's always false
}
}
}
<Window ... Name="MyWindow">
<Grid>
<CheckBox ... IsChecked="{Binding ElementName=MyWindow, Path=ShowPending}"/>
</Grid>
</Window>
Note i added a name to <Window>, and changed the binding in your CheckBox. You will need to implement ShowPending as a DependencyProperty as well if you want it to be able to update when changed.
Addendum to #Will's answer: this is what your DependencyProperty might look like (created using Dr. WPF's snippets):
#region ShowPending
/// <summary>
/// ShowPending Dependency Property
/// </summary>
public static readonly DependencyProperty ShowPendingProperty =
DependencyProperty.Register("ShowPending", typeof(bool), typeof(MainViewModel),
new FrameworkPropertyMetadata((bool)false));
/// <summary>
/// Gets or sets the ShowPending property. This dependency property
/// indicates ....
/// </summary>
public bool ShowPending
{
get { return (bool)GetValue(ShowPendingProperty); }
set { SetValue(ShowPendingProperty, value); }
}
#endregion
You must make your binding mode as TwoWay :
<Checkbox IsChecked="{Binding Path=ShowPending, Mode=TwoWay}"/>
If you have only one control that you want to bind to a property of your code-behind, then you can specify this as the source in your binding via a RelativeSource like this:
<CheckBox ...
IsChecked="{Binding ShowPending, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}">
That could be the end of the answer. But more generally you will have multiple controls and wish to bind them to various properties on your class. In this case it is neater and more convenient to make use of the fact that the DataContext property (which is the default source object for data binding) is inherited down through the control hierarchy, so setting it at the top level will make it available to all the child controls.
There is no default value for DataContext, but there are at least two ways you can set the DataContext property of your Window element to point at itself:
By setting DataContext = this in the code-behind constructor. This is very simple, but some might argue that it's not clear in the XAML where the DataContext is pointing.
By setting the DataContext in XAML using DataBinding
The simplest and, I think, most elegant way to set the DataContext at the Window/UserControl level in XAML is very straight forward; just add DataContext="{Binding RelativeSource={RelativeSource Self}}" to your Window element. RelativeSource Self just means "bind directly to the object", which in this case is the Window object. The lack of a Path property results in the default Path, which is the source object itself (i.e. the Window).
<Window x:Class="MyProject.Form1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<CheckBox ...
IsChecked="{Binding ShowPending}">
</CheckBox>
</Grid>
</Window>
Once you have done this, the DataContext property for all child controls will be the Window class, so data binding to properties in your code-behind will be natural.
If for some reason you don't want to set the DataContext on the Window, but wish to set it lower down the control hierarchy, then you can do so by using the FindAncestor mechanism. E.g. if you want to set it on the Grid element and all children of the Grid:
<Grid DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}">
<CheckBox ...
IsChecked="{Binding ShowPending}">
</CheckBox>
</Grid>
It's probably worth noting at this point that what we have achieved so far is the ability to bind a UI Control to a property of your code-behind class, and for that code-behind property to be kept up-to-date with changes to the UI element. So if the user checks the CheckBox, the ShowPending property will be updated.
But quite often you also want the reverse to be true; a change to the source property should be reflected in a corresponding change to the UI Control. You can see this by adding another CheckBox control to your window, bound to the same ShowPending property. When you click one checkbox, you would probably hope or expect the other Checkbox to be synchronized, but it won't happen. To achieve this your code-behind class should either (a) implement INotifyPropertyChanged, (b) add a ShowPendingChanged event or (c) make ShowPending a Dependency Property. Of the 3, I suggest implementing INotifyPropertryChanged on your code-behind is the most common mechanism.

Resources