WPF Binding to ElementName inside ItemsControl - wpf

I have a checkbox, and an ItemsControl populating several DataGrids the following way:
<Checkbox Content="Birthday Column Visible" x:Name="UI_BirthdayVisibleCB" />
<ItemsControl ItemsSource="{Binding Path=ParentsCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Children}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Birthday" Width="120" Visibility="{Binding IsChecked, ElementName=UI_BirthdayVisibleCB, Converter={StaticResource BoolToVis}}" >
...
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Rest of closing tags>
This creates binding output errors as it tries to find IsChecked on the DataGridTemplateColumn. If I try to search for a Relative Ancestor I receive the exception:
Binding.RelativeSource cannot be set while using Binding.ElementName.
I have a ViewModel, and stick to MVVM mostly, but in this case I'd really like to keep the column visibilities on the View layer. Note that BoolToVis just converts Boolean to Visibility.
Edit
Here is an example of what I'm trying to do:
<DataGridTemplateColumn Header="Birthday" Visibility="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MyView} }, Path=IsChecked, ElementName=UI_BirthdayVisibleCB, Converter={StaticResource BoolToVis}}" />
It compiles but doesn't run however, it throws the exception above.

You are using RelativeSource, which can't be mixed with ElementName, but you once you have the correct RelativeSource, you can drill down deeper using path.
e.g.
Visibility="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MyView} }, Path=UI_BirthdayVisibleCB.IsChecked, Converter={StaticResource BoolToVis}}"
presumably you have some xaml like this:
<UserControl class="MyView" ... >...<CheckBox Name="UI_BirthdayVisibileCB"/> ...
The above binding should find this UserControl by type based on RelativeSource, then it will try to find a property named UI_BirthdayVisibleCB, which it won't find because WPF XAML implements this named element as a field.
The easy work around is to go into your codebehind and expose a property for it.
public object BirthdayVisibileCB_4_binding {
get { return UI_BirthdayVisibileDB; }
}
and bind to it instead:
Visibility="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type local:MyView} },
Path=BirthdayVisibileCB_4_binding.IsChecked, Converter={StaticResource BoolToVis}}"
Yes, it kind of a pain to do this, but MVVM only matches WPF so far... its not a great fit, its only the best fit we have around.

If you want to try RelativeSource, you have to remove ElementName from the declaration:
However, only one of the three
properties, ElementName, Source, and
RelativeSource, should be set for each
binding, or a conflict might occur.
This property throws an exception if
there is a binding source conflict.
http://msdn.microsoft.com/en-us/library/system.windows.data.binding.elementname.aspx
Your usage of ElementName seems correct, so I'll continue to look at the problem if you prefer that over RelativeSource.

Related

Binding ListBox Items using Templates

I am going crazy trying to figure this out without success.
I have a DependencyObject, ("UserObject"). It has a "DataItems" DependecyProperty that is an ObservableCollection. "UserDefiniton" is a DependencyObject with a DependencyProperty of "Data". Data has two properties: DataType (an enumeration) and Value (a string).
I am trying to define a ListBox in XAML that uses the "DataItems" property as its ItemsSource. In the ItemTemplate, I have several different controls. For simplicity of this issue, I am using a CheckBox and a TextBox. I want CheckBox to be available and visible when DataType is 0, while I want the TextBox to be available and visible when the DataType is 1. Only one control can be available and visible at a time.
This works:
<ListBox
ItemsSource={Binding DataItems, Mode=OneWay}>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<CheckBox
Visibility="{Binding Path=Data.DataType, Mode=OneWay, Converter={StaticResource VisibilityConverter}, ConverterParameter=0}"
IsChecked="{Binding Path=Data.Value, Mode=TwoWay, Converter={StaticResource StringToBoolean}}" />
<TextBox
Visibility="{Binding Path=Data.DataType, Mode=OneWay, Converter={StaticResource VisibilityConverter}, ConverterParameter=1}"
Text="{Binding Path=Data.Value, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
<Listbox.ItemTemplate>
</ListBox>
The problem is that even though only one is visible, both are fighting over the Data.Value property (the boolean of the checkbox will show in the textbox, even though the checkbox is hidden).
Basically, though, the binding in this case is working--but the implementation is incorrect.
So, I switched to using Templates. The problem I am having is that I can't get the binding to work.
This is the code that I have for the Template. The Template selector is working correctly, but the Text property of the TextBox and the IsChecked property of the checkbox are not binding to Data.Value:
<DataTemplate x:Key="TextBoxItem">
<TextBox
Text="{Binding Path=Data.Value, Mode=TwoWay}" />
</DataTemplate>
<DataTemplate x:Key="CheckBoxItem">
<CheckBox
IsChecked="{Binding Path=Data.Value, Mode=TwoWay, Converter={StaticResource StringToBoolean}}" />
</DataTemplate>
...
<ListBox
ItemsSource={Binding DataItems, Mode=OneWay}>
<ListBox.ItemTemplate>
<DataTemplate>
<ContentControl
Content="{Binding Path=Data.DataType, Mode=OneWay}"
ContentTemplateSelector="{DynamicResource UserDefinitionTemplateSelector}"/>
</DataTemplate>
<ListBox.ItemTemplate>
</ListBox>
So how do I fix the binding?
Content should be set to {Binding}, since the Content will be the DataContext of the data-templates, hence you should just pass on the current DataContext. If you need to pass specific data to the template selector you can just drill down in the whole object.
There also is a template selector on the level of the ListBox, so you do not really need the internal ContentControl.
(You might also be interested in generic methods of debugging data bindings.)

Get UserControl DataContext properties to bind to from within the control

Here is the code for me UserControl:
<UserControl x:Class="UZ.ActivitySink.GUI.Views.POSsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Views="clr-namespace:UZ.ActivitySink.GUI.Views">
<DockPanel>
<TreeView ItemsSource="{Binding Types}" x:Name="POSTree" Background="{x:Null}" HorizontalAlignment="Left" FontSize="14"
Visibility="{Binding DataContext.TreeVisibility, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Views:POSsView}}, Mode=TwoWay}">
</TreeView>
<StackPanel x:Name="ErrorPanel"
Visibility="{Binding DataContext.ErrorMessageVisibility, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Views:POSsView}}, Mode=TwoWay}" Margin="20">
</StackPanel>
</DockPanel>
</UserControl>
I am assigning a datacontext object to the control in it's constructor
DataContext = _viewModel;
_viewModel has the properties named TreeVisibility and ErrorMessageVisibility of type Visibility, but still the Visual elements on the screen don't bind their visibility values to these properties.
What is the correct way to reference the controls' viewmodel properties from xaml declaration in my case?
Thank you.
Your bindings are more complicated than necessary.
This one:
Visibility="{Binding DataContext.TreeVisibility,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Views:POSsView}}, Mode=TwoWay}"
in this case should be equivalent to the much simpler
Visibility="{Binding TreeVisibility}"
That said, even though the current bindings are complicated they should still have worked (at least given the information you already provide).
If you still can't get them to work, run your app in the debugger and look at the Output window -- binding errors are reported there by default, and they contain information that will help you get to the root of the problem.

WPF Datagrid ComboBox DataBinding

Can anyone tell me why this works;
<DataGridTemplateColumn Header="Supplier">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox DisplayMemberPath="SupplierName" SelectedValuePath="SupplierID"
SelectedValue="{Binding SupplierID}"
ItemsSource="{Binding Path=DataContext.Suppliers, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
but this doesn't;
<DataGridComboBoxColumn Header="Combo" DisplayMemberPath="SupplierName" SelectedValuePath="SupplierID"
SelectedValueBinding="{Binding SupplierID}"
ItemsSource="{Binding Path=DataContext.Suppliers, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
Second snippet does not show the list of SupplierName on edit...
It's because a DataGridComboBoxColumn isn't a user interface element, but ComboBox is.
In the first example, because your ComboBox is part of the visual tree, RelativeSource can do what it's supposed to do: walk up the UI tree looking for the item you've asked for. But in the second example, the DataGridComboBoxColumn is a DependencyObject but it's not an actual UI element - it's an object that describes something about the UI element.
You could try using ElementName instead, and give a name to your root window. Or, you might be able to get away with just:
<DataGridComboBoxColumn ...
ItemsSource="{Binding Path=Suppliers}" />
The DataContext will flow down from the window to the grid, so unless you've overidden it with something else at this point in the UI, it'll still be available.
Or if that doesn't work, you might want to add the relevant collection to a resource dictionary so you can get it with a Source={StaticResource suppliers} in the binding.
The reason is that the ItemsSource for the DataGridComboBoxColumn can not be found.
You will need to use the RelativeSource Binding and point it to the correct DataContext AncestorType. This will take some trial and error to find the DataContext that contains your list to satisfy your ItemsSource.

WPF Treeview contextmenu IsChecked binding MVVM

I've got a TreeView to which I associate a ContextMenu. That contextmenu has an item whose IsChecked property I want to bind to my ViewModel. Since I am using a tree each treeitem is bound to a subproperty of my ViewModel.
In the VS2010 output window I am seeing this databinding error:
BindingExpression path error: 'IsAutoStart' property not found on 'object' ''HostMgmtViewModel' (HashCode=12565727)'. BindingExpression:Path=IsAutoStart; DataItem='HostMgmtViewModel'
This clearly shows it is trying to bind to my ViewModel and not to the treeitem's associated data. How do I bind to the correct object? Remember my contextmenu is associated with the whole TreeView not to the specific treeitem.
---------- Edit
As xandy pointed out below the resolution to my problem was to bind the IsChecked like this:
{Binding Path=PlacementTarget.SelectedItem.IsDisabledStart, Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}
<TreeView Name="tview" Grid.Row="0" Tag="{Binding RelativeSource={RelativeSource Self}, Path=SelectedItem}">
<TreeView.ContextMenu>
<ContextMenu>
<MenuItem Name="miC" Header="{Binding Path=Tag.Key}" DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"></MenuItem>
</ContextMenu>
</TreeView.ContextMenu>
</TreeView>
This is the working code snippet I have. Courtesy of [this].1 All you need is to change the binding path in the tag. I am currently binding the Treeview to a dictionary, so it is the Key property of it. It should not have any problem in binding to any object collections. One interesting finding is context menu is not in part of element tree and this cause the problem. I could bind the text box with no problem:
<TextBlock Grid.Row="1" DataContext="{Binding ElementName=tview, Path=SelectedItem}">
<TextBlock.Text>
<Binding Path="Key" />
</TextBlock.Text>
</TextBlock>
But it is not functioning if for menuitem if I put the same thing.

Bind to ItemsControl's DataContext from inside an ItemTemplate

I have an ItemsControl whose for the ItemTemplate DataTemplate contains a Button. I want the Command on the button to bind to a Command on the DataContext of the ItemsControl, not the ItemTemplate. I think the solution has to do with using RelativeSource, but my attempts so far have failed:
<ItemsControl ItemsSource="{Binding Games}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command="{Binding Path=GameSelectedCommand, Source={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}}"
CommandParameter="{Binding}"
Style="{StaticResource MenuButtonStyle}"
Content="{Binding Name}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
How can I get the Button to bind to the GameSelectedCommand of the ItemsControl's DataContext object?
You're setting the source of the binding to the ItemsControl itself. Therefore, you'll need to dereference the DataContext of the ItemsControl:
Command="{Binding DataContext.GameSelectedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}}"
How would you have known this? Take a look at your debug output window when running the app. You'll see a message along the lines of "Cannot resolve property 'GameSelectedCommand' on type 'ItemsControl'".

Resources