Get reference to an item in a HeaderedItemsControl - wpf

XAML:
<HeaderedItemsControl ItemsSource="{Binding FooCollection}">
<HeaderedItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text={Binding foo}/>
<Button Command="{Binding DeleteFoo}">Delete</Button>
</StackPanel>
</DataTemplate>
</HeaderedItemsControl.ItemTemplate>
Given this XAML, how can I get a reference to the item clicked in FooCollection, in the DeleteFoo command & method, in the ViewModel? Normally in a DataGrid etc, it would just be the SelectedItem bound in your ViewModel, but annoyingly the HeaderedItemsControl doesn't have this option.
Is there another way to pass a reference to the item, or its index position in FooCollection via CommandArguments for example?
I realise that the ListView is a similar control, that has a selectedItem equivalent, however I've got a nicely formatted HeaderedItemsControl set up (with a title header) so I'd prefer to not have to ditch that if possible.
Thanks very much in advance for any help.

I'll put it as an answer anyway.. ItemsControls set the DataContext of the ListItem to the object that they represent so you can probably get the reference to the clicked item using
<HeaderedItemsControl ItemsSource="{Binding FooCollection}">
<HeaderedItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text={Binding foo}/>
<Button Command="{Binding DeleteFoo}" CommandParameter={Binding}>Delete</Button>
</StackPanel>
</DataTemplate>
</HeaderedItemsControl.ItemTemplate>

You can send the DataContext of each item to the command via CommandParameter
<Button Command="{Binding DeleteFoo}" CommandParameter={Binding}>Delete</Button>

Related

Binding a button nested in DataTemplate

I need to bind a button's command inside a datatemplate like below:
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Content="-" Cursor="Hand" Width="50"
Background="Red" x:Name="removeButton"
Command="{Binding Remove}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
Unfortunately it does not work. How can I bind a command in a button insade a datatemplate?
I found that thread in the forum:
Bindings in nested WPF DataTemplates
but the method given by person, who answered this question, does not work as well. I think, that something has changed in WPF since this time, I would you grateful for your help.
If Remove is defined in the view model of the parent ListView, you could bind to it using a RelativeSource:
Command="{Binding DataContext.Remove,
RelativeSource={RelativeSource AncestorType=ListView}}"
You could also set the AncestorType to Window or UserControl depending on where the command property is defined and where the DataTemplate is applied.

WPF MVVM Light dynamic views and datagrids

Okay I have a mildly complex piece of functionality here. I'd like to know A) If I am doing it properly. If not, what should I change? 2) If it is proper, what is the best solution to my problem?
I have a main window with a ListView of items. If I click one of these, the right hand Grid Column in this window should populate with a DataGrid with information on the item selected. If I click another item in the ListView, it should change to another DataGrid.
I have seen some ContentPresenter examples but I cannot get this to work, so I stripped it out and will show you the code I have so far. Right now, I only have one item setup, so I will stick with this Driver example.
DriverGrid.xaml
<UserControl DataContext="{Binding AdminDriver, Source={StaticResource Locator}}">
<Grid>
<DataGrid>
//stuff here for datagrid
</DataGrid>
<Button Content="Edit" Command="{Binding ShowEditWindow}" />
<Button Content="Add" Command="{Binding ShowAddWindow}"/>
</Grid>
</UserControl>
AdminDriver.cs (VM)
//Contains variables, and is the datacontext for the above usercontrol
AdminMain.xaml (view for all admin stuff)
//Contains a bunch of junk for the min admin screen which has the listview with the options in it. If you require this piece of code, I can trim it down but I don't se currently see it's relevance. The DataGrid belongs in this window, I'm assuming in a Content Presenter. Here is the ListView that is in column 1 of 2.
<ListView ItemsSource="{Binding AdminMenu}"
Name="AdminFields"
SelectionMode="Single">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventToCommand CommandParameter="{Binding SelectedItem, ElementName=AdminFields}" Command="{Binding registerSelected}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListView.ItemTemplate>
<ItemContainerTemplate>
<TextBlock Text="{Binding FieldName}"/>
</ItemContainerTemplate>
</ListView.ItemTemplate>
</ListView>
I would take the binding of the DataContext out of the UserControl. This will allow you to use it in other spots if you ever find the need. Instead, just bind the DataContext where you are using it.
<view:YourUserControl DataContext="{Binding AdminDriver}" />
If you are looking to present different UserControls based upon which item is selected in the ListView, then you'll use a ContentPresenter. All the items in your ItemsSource will have the same base class or implement the same interface so that you can put them in the same ObservableCollection. I'll assume AdminDriver would be of that type/interface as well then.
You would set up some DataTemplates at the top of the Window that map the possible real types of the objects in your ItemsSource (AdminMenu) to the UserControl that would represent them.
<Window.Resource>
<DataTemplate DataType="{x:Type model:TypeA}">
<view:UserControlA />
</DataTemplate>
<DataTemplate DataType="{x:Type model:TypeB}">
<view:UserControlB />
</DataTemplate>
//rinse and repeat
</Window.Resource>
Then you'll add a ContentPresenter to the Grid and bind it's DataContext to the AdminDriver property. The UserControl matching the actual type of the selected item as mapped in your DataTemplates will appear.
<ContentPresenter Content="{Binding AdminDriver}" />

How to override the ItemsSource defined for an ItemTemplate?

I use an Item Template to define how the rows of my grid must be displayed. The grid definition (simplified) shows that the item template source is GridRows (a collection of rows) :
<grid ...>
(...)
<ScrollViewer
ItemTemplate="{StaticResource GridRowItemDataTemplate}"
ItemsSource="{Binding GridRows}" />
</ScrollViewer>
</grid>
So far, so good.
In the item template, the textbox is bound to ImportZoneName, which resolved of course to GridRows[i].ImportZoneName, and this is exactly what I want :
<DataTemplate x:Key="GridRowItemDataTemplate">
<Grid>
<TextBlock {Binding ImportZoneName}" />
<ComboBox
SelectedItem="{Binding SelectedModelingTypeValue}"
ItemsSource="{Binding ModelingTypes}" />
</Grid>
</DataTemplate>
Now the problem : I also want to bind the combo box to an other property (ModelingTypes) of my view model. This property is not linked in any way to GridRows. How can I tell WPF to override (or forget) the item template source?
Many, many thanks !
BTW, I did not find yet a simple guide for these simple binding cases... If anyone have a link to such a guide, I will bless him/her forever :)
You can get the DataContext of the parent list like this:
<DataTemplate x:Key="GridRowItemDataTemplate">
<Grid>
<TextBlock {Binding ImportZoneName}" />
<ComboBox
SelectedItem="{Binding SelectedModelingTypeValue}"
ItemsSource="{Binding DataContext.ModelingTypes,
RelativeSource={RelativeSource FindAncestor,
AncestorType=Grid}}" />
</Grid>
</DataTemplate>
Replace Grid with the type of the grid you are using (not sure which it is, not evident from the question), as long as its DataContext has the property ModelingTypes

Relative Binding in Silverlight

I have an ItemsControl in my application. The page associated with the ItemsControl is bound to a view-model. The view-model includes two properties: People and Options. For each person, I want to display a list of options in a ComboBox. The options are defined in my view-model. My code looks like the following:
<ItemsControl ItemsSource="{Binding Path=People}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ComboBox ItemsSource="Options" DisplayMemberPath="FullName" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
However, because each Item represents a Person, the ComboBox is looking at the Person object for a property called "Options". How do I reference the view model for the from the ComboBox instead of the Person?
Thanks!
You can use the following technique
<ComboBox ItemsSource="{Binding ElementName=LayoutRoot, Path=DataContext.Options}" DisplayMemberPath="FullName" />
Assuming that your LayoutRoot's DataContext is the View Model. If not you can give your ItemsControl a name and use that for the ElementName.

Pattern for working with a form in Silverlight 4? (How to get references to XAML elements)

I have a form:
<StackPanel Orientation="Horizontal" Visibility="{Binding Editable, Converter={StaticResource visibilityConverter}}"
ToolTipService.ToolTip="Add new topic to this group">
<sdk:AutoCompleteBox Width="160" ItemsSource="{Binding ElementName=LayoutRoot, Path=DataContext.TopicNames}" />
<Button Click="addTopicButton_Click">
<Image Source="Images/appbar.add.rest.png" />
</Button>
</StackPanel>
This form appears in a DataTemplate for an ItemsControl. I'm not sure what the best way is to get the data from the AutoCompleteBox when the button is clicked. I can't give the elements x:Name attributes, because they're in a template (right?).
How can I get around this? The Click event will give me the Button, but I need a reference to the text box. Use the Button's parent, then look through the children for the Textbox? If I factored this out into its own UserControl, I could set x:Name values, but I'd rather not do that.
Any other ideas?
Update: Here is another example of such a problem:
<ListBox x:Name="topicList"
ItemsSource="{Binding Id, Converter={StaticResource topicGroupIDConverter}}"
SelectionChanged="ListBox_SelectionChanged"
HorizontalAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"
Width="150"
VerticalAlignment="Center"
ToolTipService.ToolTip="{Binding Description}"
ToolTipService.Placement="Right" />
<Button ToolTipService.ToolTip="Remove this topic from this group"
Visibility="{Binding ElementName=topicList,
Path=DataContext.Editable,
Converter={StaticResource visibilityConverter}}"
Click="removeTopicButton_Click"
HorizontalAlignment="Right"
Margin="10,0">
<Image Source="Images/appbar.cancel.rest.png" />
</Button>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
When the button is clicked, I want to access topicList.DataContext. However, topicList itself is a DataTemplate in an ItemsControl, so I can't access it using its name from code-behind. How else can I do this?
You can add a property, say SelectedItemInAutoCompleteBox, to your presenter, and then can bind it to the SelectedItem property of AutoCompleteBox, using Mode=TwoWay, like this,
<sdk:AutoCompleteBox SelectedItem="{Binding Path=DataContext.SelectedItemInAutoCompleteBox, Mode=TwoWay}" ... />
You may try the same approach with Text property of AutoCompleteBox, also. See if it solves your problem.:-)
You have several choices:
If you're on Silverlight 5, use the AncestorBinding
Otherwise, use a Silverlight 4 AncestorBinding hack (it doesn't look pretty)
Or you could try DataContextProxy, which stores the DataContext in a resource so that it is accessible. Note: you should set the DataContextProxy as a Resource of topicList ListBox, not the UserControl as in Dan Wahlin's example.

Resources