Based on this answer I tried to use the following code to achieve a ComboBox with different templates depending on whether or not the drop down is open.
The ComboBox definition looks like this:
<ComboBox SelectedValuePath="Id"
DisplayMemberPath="Name">
<ComboBox.Resources>
<DataTemplate x:Key="SelectedTemplate">
<TextBlock Text="Abbreviation" />
</DataTemplate>
<DataTemplate x:Key="DropDownTemplate">
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Border}}, Path=ActualWidth, Mode=OneTime}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="20" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}, Path=DisplayMemberPath}" />
<TextBlock Grid.Column="1"
Text="Abbreviation" />
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
<infrastructure:ComboBoxItemTemplateSelector x:Key="ComboBoxItemTemplateSelector"
DropDownDataTemplate="{StaticResource DropDownTemplate}"
SelectedDataTemplate="{StaticResource SelectedTemplate}" />
</ComboBox.Resources>
</ComboBox>
and the ComboBoxItemTemplateSelector looks like this:
public class ComboBoxItemTemplateSelector : DataTemplateSelector
{
public DataTemplate DropDownDataTemplate { get; set; }
public DataTemplate SelectedDataTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
return DependencyObjectHelper.GetVisualParent<ComboBoxItem>(container) != null ? this.DropDownDataTemplate : this.SelectedDataTemplate;
}
}
Now the problem is that somehow SelectTemplate is never called, even although I checked all the DynamicResources and StaticResources.
You need to assign the ComboBoxItemTemplateSelector to the ComboBox like this:
<ComboBox ItemTemplateSelector="{DynamicResource ComboBoxItemTemplateSelector}">
and you cannot set the DisplayMemberPath or the SelectTemplate method will never be called.
Related
I get this error :
System.Windows.Data Error: 40 : BindingExpression path error: 'OpenPopupCommand' property not found on 'object' ''String' (HashCode=62725275)'. BindingExpression:Path=OpenPopupCommand; DataItem='String'
when I added a parameter to my command:
OpenPopupCommand = new RelayParamCommand((e) => PopupVisibility(FilterButton) );
VM:
private void PopupVisibility(object sender)
{
Console.WriteLine(sender.ToString());
PopupVisible ^= true;
}
Think is that I added Filter Button to Datagrid Headers which are generated automatically. Now I want to open popup when button is clicked. But think is that is not working, because i have to pass button by buttons x:Name to Popup PlacementTarget parameter.
<Page.DataContext>
<PDB:UsersViewModel x:Name="vm"/>
</Page.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!--Page Header info content-->
<Grid Grid.Row="0">
<StackPanel Orientation="Horizontal">
<TextBlock Margin="2" Text="{Binding ElementName=userPage, Path=Name}"/>
<TextBlock Margin="2" Text="{Binding SelectedUser.Name}"/>
<TextBlock Margin="2" Text="{Binding ElementName=myGrd, Path=CurrentColumn.DisplayIndex}"/>
<Button x:Name="mybtn"
Content="{Binding Filters.Count, Mode=OneWay}"
Visibility="{Binding Filters.Count, Converter={Wpf:VisibilityConverter}}"
/>
</StackPanel>
</Grid>
<!--Datagrid content-->
<DataGrid x:Name="myGrd"
SelectionMode="Single"
SelectionUnit="Cell"
CurrentItem="{Binding SelectedUser, Mode=TwoWay}"
CurrentColumn="{Binding CurrentColumn, Mode=TwoWay}"
IsReadOnly="True"
Grid.Row="1"
ItemsSource="{Binding FilteredUserList}"
AutoGenerateColumns="True"
CanUserAddRows="False"
>
<DataGrid.Resources>
<!--Popup-->
<ContextMenu x:Key="ContextMenu">
<ContextMenu.Items>
<MenuItem Header="Filter by Selection" Command="{Binding IncludeCommand, Source={x:Reference vm}}"/>
<MenuItem Header="Filter exclude Selection" Command="{Binding ExcludeCommand, Source={x:Reference vm}}"/>
<MenuItem Header="Remove all Filters" Command="{Binding RemoveAllFiltersCommand, Source={x:Reference vm}}" Visibility="{Binding Filters.Count, Source={x:Reference vm}, Converter={Wpf:VisibilityConverter}}"/>
</ContextMenu.Items>
</ContextMenu>
<!--Custom Datagrid header View-->
<Style TargetType="DataGridColumnHeader" x:Name="FilterHeader">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel>
<TextBox Margin="0,0,0,10" Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridColumnHeader}}, Path=Width}" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" HorizontalAlignment="Center"/>
<Button Name="FilterButton"
Content="[-F-]"
Command="{Binding OpenPopupCommand}"
CommandParameter="{Binding ElementName=FilterButton}"/>
<Popup Name="MyPopup"
StaysOpen="False"
Placement="Right"
IsOpen="{Binding PopupVisible}"
PlacementTarget="{Binding FilterButton}">
<Border Background="White" BorderBrush="Black" Padding="5" BorderThickness="2" CornerRadius="5">
<StackPanel Orientation="Vertical">
</StackPanel>
</Border>
</Popup>
</StackPanel>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.Resources>
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="ContextMenu" Value="{StaticResource ContextMenu}"/>
</Style>
</DataGrid.CellStyle>
</DataGrid>
</Grid>
By this approach i want to pass clicked button as parameter to VM and bind it to Popup PlacementTarget parameter. What i am doing wrong? Can i pass clicked button parameter? I know i break mvvm rules when passing view to vm, but how to achieve what i want, when i don't want to define every column in datagrid. Thank you
Your bindings have the DataContext of the DataGridColumnHeader as source which is the value of DataGridColumn.Header. This value in your case is a string and not your expected view model. This is the reason why your bindings doesn't resolve and you get the error message (which is exactly telling you this).
To fix the binding you have to find the next parent element that has the required DataContext, which is I assume the DataGrid:
<Button Name="FilterButton"
Content="[-F-]"
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}, Path=DataContext.OpenPopupCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}}"/>
When checking your code I can see the command which is bound to the Button is toggling the PopupVisible property. I therefore suggest to remove this view related code from the view model and replace the Button with a ToggleButton which binds directly to the Popup.IsOpen:
<!--Custom Datagrid header View-->
<Style TargetType="DataGridColumnHeader">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel>
<TextBox Margin="0,0,0,10"
Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridColumnHeader}}, Path=Width}" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}"
HorizontalAlignment="Center" />
<ToggleButton Name="FilterButton"
Content="[-F-]" />
<Popup Name="MyPopup"
StaysOpen="False"
Placement="Right"
IsOpen="{Binding ElementName=FilterButton, Path=IsChecked}"
PlacementTarget="{Binding ElementName=FilterButton}">
<Border Background="White"
BorderBrush="Black"
Padding="5"
BorderThickness="2"
CornerRadius="5">
<StackPanel Orientation="Vertical">
</StackPanel>
</Border>
</Popup>
</StackPanel>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
To get the cell values of all rows contained in a single column where the column header value is the parameter and maps to a property name requires reflection. You need to bind a ListView to the resulting column value collection which uses an ItemTemplate to add a CheckBox to the item. The final version should be as follows:
<!--Custom Datagrid header View-->
<Style TargetType="DataGridColumnHeader">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel>
<TextBox Margin="0,0,0,10"
Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridColumnHeader}}, Path=Width}" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding RelativeSource={RelativeSource Self}}"
HorizontalAlignment="Center" />
<ToggleButton Name="FilterButton"
Content="[-F-]"
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}, Path=DataContext.(UsersViewModel.GenerateFilterViewItemsCommand)}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=DataGridColumnHeader}, Path=Content}" />
<Popup Name="MyPopup"
StaysOpen="False"
Placement="Right"
IsOpen="{Binding ElementName=FilterButton, Path=IsChecked}"
PlacementTarget="{Binding ElementName=FilterButton}">
<Border Background="White"
BorderBrush="Black"
Padding="5"
BorderThickness="2"
CornerRadius="5">
<ListView
ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}, Path=DataContext.(UsersViewModel.FilterViewItems)}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsIncluded, Mode=OneWayToSource}"
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}, Path=DataContext.(UsersViewModel.IncludeItemCommand)}"
CommandParameter="{Binding}" />
<ContentPresenter Content="{Binding CellValue}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Border>
</Popup>
</StackPanel>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
To map the filter list to the actual items requires an additional type to hold the information:
Predicate.cs
public class FilterPredicate
{
public FilterPredicate(int rowIndex, object cellValue, object columnKey)
{
this.RowIndex = rowIndex;
this.CellValue = cellValue;
this.ColumnKey = columnKey;
}
public int RowIndex { get; set; }
public object CellValue { get; set; }
public bool IsIncluded { get; set; }
public object ColumnKey { get; set; }
}
You also need to add a collection for the filter view and one for the included item indices to the view model:
UsersViewModel.cs
public class UsersViewModel
{
public UsersViewModel()
{
this.FilterViewItems = new ObservableCollection<FilterPredicate>();
this.IncludedItemsIndex = new Dictionary<object, List<int>>();
}
// The binding source for the Popup filter view
public ObservableCollection<FilterPredicate> FilterViewItems { get; set; }
private Dictionary<object, List<int>> IncludedItemsIndex { get; set; }
public ICommand GenerateFilterViewItemsCommand => new RelayParamCommand((param) =>
{
var columnHeader = param as string;
this.IncludedItemsIndex.Remove(columnHeader);
this.FilterViewItems.Clear();
for (var rowIndex = 0; rowIndex < this.FilteredUserList.Count; rowIndex++)
{
var data = this.FilteredUserList.ElementAt(rowIndex);
var columnValue = data.GetType()
.GetProperty(columnHeader, BindingFlags.Public | BindingFlags.Instance)?
.GetValue(data);
if (columnValue != null)
{
this.FilterViewItems.Add(new FilterPredicate(rowIndex, columnValue, columnValue));
}
}
});
public ICommand IncludeItemCommand => new RelayParamCommand((param) =>
{
var predicate = param as FilterPredicate;
if (predicate.IsIncluded)
{
if (!this.IncludedItemsIndex.TryGetValue(predicate.ColumnKey, out List<int> includedIndices))
{
includedIndices = new List<int>();
this.IncludedItemsIndex.Add(predicate.ColumnKey, includedIndices);
}
includedIndices.Add(predicate.RowIndex);
}
else
{
if (this.IncludedItemsIndex.TryGetValue(predicate.ColumnKey, out List<int> includedIndices))
{
includedIndices.Remove(predicate.RowIndex);
}
}
// Apply the filter
CollectionViewSource.GetDefaultView(this.FilteredUserList).Filter =
data => this.IncludedItemsIndex.Values
.SelectMany(indices => indices)
.Contains(this.FilteredUserList.IndexOf(data as FilteredUser));
});
}
Since you are autogenerating columns, I strongly suspect it is trying to find a OpenPopupCommand in one of the string properties on the items for FilteredUserList.
Try The Following :
Give you'r Page or DataGrid a Name, And change your Binding to
<MenuItem Command="{Binding Path=DataContext.IncludeCommand, Source={x:Reference myPage}}"/>
Here is what I am trying to accomplish - I have an Item control with its item source set to ObservableCollection Each item in this collection is used as a viewModel to create different buttons in ItemControl. I would like to know how can I bind to a property of this viewmodel(PersonViewModel) from button style template? Lets say, I want to control visibility of a specific element in my custom button with a property defined in PersonViewModel. Here is a little sample code:
public class MainViewModel : ViewModelBase
{
private ObservableCollection<PersonViewModel> _personViewModelList;
public ObservableCollection<PersonViewModel> PersonViewModelList
{
get => _personViewModelList;
set
{
_personViewModelList= value;
OnPropertyChanged("PersonViewModelList");
}
}
}
public class PersonViewModel
{
private bool _visible;
public bool Visible
{
get => _visible;
set
{
_visible= value;
OnPropertyChanged("Visible");
}
}
}
Here is my item control:
<ItemsControl ItemsSource="{Binding PersonViewModelList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Width="360" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button
Style="{StaticResource ImageButton}">
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And here is my custom button style:
<Style x:Key="ImageButton" TargetType="Button">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="20" />
</Grid.ColumnDefinitions>
//Here I want to bind to "Visible" property in PersonViewModel class. Any ideas on how to accomplish it?
<TextBlock Visibility="{Binding...}" Grid.Row="0" Grid.Column="1" />
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
You could use something like this:
<TextBlock Visibility="{Binding DataContext.Visible, Converter={StaticResource BooleanToVisibilityConverter}, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Button}}" Grid.Row="0" Grid.Column="1" />
The problem is that the ContentPresenter of your Button has DataContext = null.
I have a listbox and all I want to do is collapse the listboxitem based on a boolean property of my SelectedItem.
The IsVisible property on my client Model implements the NotifyPropertyChanged event.
Overview - I have a list of clients which users can do CRUDs on. When they delete, I set a boolean property on the Model which my VM exposes to the View. This should then only hide the 'deleted' row from the list. During a flush to db I CRUD based on the mode of the model.
<ListBox Name="listClients"
Grid.Column="1" Grid.Row="1"
Margin="0" BorderThickness="0"
Height="auto"
Style="{StaticResource BumListBox}"
SelectionMode="Extended"
ItemsSource="{Binding Path=ClientList}"
SelectedItem="{Binding SelectedClient, Mode=TwoWay}"
Tag="{Binding DataContext, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}" >
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsVisible}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="50"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding ClientNo}" Foreground="White" FontSize="{StaticResource HeaderFontSize}" VerticalAlignment="Center" />
<TextBlock Grid.Column="1" Text="{Binding ClientDesc}" Foreground="White" FontSize="{StaticResource SubHeaderFontSize}" FontWeight="Light" VerticalAlignment="Center" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Code behind to jippo MVVM process:
private void Button_Click_1(object sender, RoutedEventArgs e)
{
if (_cvm.SelectedClient != null)
{
_cvm.SelectedClient.IsVisible = !_cvm.SelectedClient.IsVisible;
_cvm.CurrentSelectedIsVisible = _cvm.SelectedClient.IsVisible; //<- another option to bind to
}
}
I've tried the these suggestions here and here or something similar but I just can't get to hide the items.
Any help in the right direction would be great, cheers.
Edit
I've tried Blam's suggestion below like this but still unable to hide the items:
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="Visibility" Value="{Binding Path=CurrentIsVisible, Converter={StaticResource b2v}}" />
</Style>
You will need to set up a converter if you are returning true/false but there is a system converter for that
Move it up to Resources
I have know I have used it this way
<ListBox x:Name="lb" ItemsSource="{Binding}" DisplayMemberPath="Text">
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="Visibility" Value="{Binding Path=Vis}" />
</Style>
</ListBox.Resources>
</ListBox>
This was rather frustrating and the solution so simple. My client model with IsVisible is in a dll and the NotifyPropertyChanged() changes were never built to update the reference in my project..so the binding never happened. These late nights are taking their toll.
I am trying to set a trigger in my DataTemplate so that when the source object has a property HasUnreadMessages set to true, then the header text goes red. But from inside the triggers in datatemplate I have no idea of how to reference the TextBlock of the header.
Full Code :
<Window x:Class="IRC.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:IRC"
Title="" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type loc:Tab}">
<DockPanel>
<ListBox x:Name="lstUsers" ItemsSource="{Binding Users}" Visibility="Collapsed" DockPanel.Dock="Right" />
<ListBox x:Name="lstMessage" ItemsSource="{Binding Messages}" />
</DockPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Type}" Value="Channel">
<Setter TargetName="lstUsers" Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding HasUnreadMessages}" Value="True">
<Setter TargetName="tabHeader" Property="Foreground" Value="Red" /> // -- ERROR
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Window.Resources>
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="_Actions">
<MenuItem Header="_Connect" InputGestureText="Alt+C" Click="Connect" />
</MenuItem>
</Menu>
<TextBox Name="txtInput" Height="22" VerticalContentAlignment="Center" SpellCheck.IsEnabled="True" DockPanel.Dock="Bottom" />
<TabControl Name="pnlTabs" ItemsSource="{Binding}" ContentTemplate="{Binding}" DockPanel.Dock="Top">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock x:Name="tabHeader" Text="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</DockPanel>
</Window>
Error :
The name "tabHeader" is not recognized.
The member "Foreground" is not recognized or is not accessible.
Tab :
public class Tab
{
public string Name { get; set; }
public string Type { get; set; }
public ObservableCollection<string> Users { get; set; }
public ObservableCollection<string> Messages { get; set; }
public bool HasUnreadMessages { get; set; }
}
You're trying to change a property of an object which is not known at that point.
Instead of putting the DataTrigger on the Tab, move the DataTrigger to the TabItem - since it's the TabItem that you're trying to change...
In your case, add a TabItem style:
<Window.Resources>
<DataTemplate DataType="{x:Type TabItem}" x:Key="ItemTemplateStyle">
<TextBlock x:Name="tabHeader" Text="{Binding Name}"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding HasUnreadMessages}" Value="True">
<Setter TargetName="tabHeader" Property="Foreground" Value="Red" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
And set this style for the TabItems:
<TabControl Name="pnlTabs" ItemsSource="{Binding}" ContentTemplate="{Binding}" DockPanel.Dock="Top"
ItemTemplate="{StaticResource ItemTemplateStyle}" />
I have a ListView in which the ListView Items have button and textblock....
Senario :
I am able to click the button with out selecting the ListView Item i.e is the selection the last Item and then if i try to click the button of the first item the first time is not selected (In DataGrid it does select).
I Cannot use DataGrid as i am using CustomView in ListView.
If you need my code for reference of the problem i'll post it..
Any help in this regard would be great
My ListView :
<ListView Name="lv"
Grid.Row="1"
DisplayMemberPath="Name"
IsTextSearchEnabled="True"
ItemsSource="{Binding}"
KeyboardNavigation.DirectionalNavigation="Cycle"
SelectionMode="Single"
TextSearch.TextPath="{Binding Path=Person.Name}"
View="{Binding Path=SelectedItem,
ElementName=viewComboBox}" />
My DataTemplates for CustomViews :
<Style x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type CustomView:PlainView},
ResourceId=ImageView}"
BasedOn="{StaticResource {x:Type ListBox}}"
TargetType="{x:Type ListView}">
<Setter Property="BorderBrush" Value="Black" />
<Setter Property="BorderThickness" Value=".5" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="ItemContainerStyle" Value="{Binding (ListView.View).ItemContainerStyle, RelativeSource={RelativeSource Self}}" />
<Setter Property="ItemTemplate" Value="{Binding (ListView.View).ItemTemplate, RelativeSource={RelativeSource Self}}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border Name="bd"
Margin="{TemplateBinding Margin}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer Margin="{TemplateBinding Padding}">
<WrapPanel KeyboardNavigation.DirectionalNavigation="Cycle"
Width="{Binding ActualWidth,
RelativeSource={RelativeSource AncestorType=ScrollContentPresenter}}"
MinWidth="{Binding (ListView.View).MinWidth,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type ListView}}}"
IsItemsHost="True"
ItemWidth="{Binding (ListView.View).ItemWidth,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type ListView}}}" Orientation="Vertical"
Height="{Binding ActualHeight,
RelativeSource={RelativeSource AncestorType=ScrollContentPresenter}}"/>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type CustomView:PlainView},
ResourceId=ImageViewItem}"
BasedOn="{StaticResource {x:Type ListBoxItem}}"
TargetType="{x:Type ListViewItem}">
<Setter Property="Padding" Value="3" />
<Setter Property="Margin" Value="5" />
<Setter Property="BorderBrush" Value="Black" />
<Setter Property="BorderThickness" Value="2" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
<DataTemplate x:Key="centralTile">
<StackPanel Width="80" Height="40" KeyboardNavigation.AcceptsReturn="True">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Button x:Name="tempabc" Command="{Binding Path=Launch}" KeyboardNavigation.AcceptsReturn="True" >
<TextBlock Text="{Binding Path=Name}" FocusManager.IsFocusScope="True"></TextBlock>
</Button>
<Image Grid.Column="1" Source="Water lilies.jpg"/>
</Grid>
<TextBlock
HorizontalAlignment="Center"
FontSize="13"
Text="{Binding Path=Name}" />
</StackPanel>
</DataTemplate>
<CustomView:PlainView x:Key="plainView"
ItemTemplate="{StaticResource ResourceKey=centralTile}"
ItemWidth="100" />
<GridView x:Key="myGridView">
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button>
<TextBlock Text="{Binding Path=Name}" />
</Button>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
As with most things, there are a number of ways to do this. Here's one I just threw together in a minute...
Given the following model:
public sealed class ItemModel
{
public string Name { get; set; }
}
I wish to display a collection of them to the user and select one via a button. This means I need three things in my ViewModel:
A collection of ItemModels
A "SelectedItem" property to hold the currently selected instance
An ICommand implementation to bind to the buttons in the View
I create my ViewModel and add these items to it. Please note, I prefer making my ViewModels extend DependencyObject rather than mess with INPC.
public sealed class ViewModel : DependencyObject
{
// 1. A collection of ItemModels
public ObservableCollection<ItemModel> ItemModels { get; private set; }
// 2. A "SelectedItem" property to hold the currently selected instance
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register(
"SelectedItem",
typeof(ItemModel),
typeof(ViewModel),
new UIPropertyMetadata(null));
public ItemModel SelectedItem
{
get { return (ItemModel)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
// 3. An ICommand implementation to bind to the buttons in the View
public Command SelectItem { get; private set; }
public ViewModel()
{
ItemModels = new ObservableCollection<ItemModel>();
ItemModels.Add(new ItemModel { Name = "One" });
ItemModels.Add(new ItemModel { Name = "Two" });
ItemModels.Add(new ItemModel { Name = "Three" });
SelectItem = new Command
{
ExecuteAction = x => SelectedItem = x as ItemModel
};
}
}
Lastly, I slap together my UI with a rudimentary ListView.
<Window
x:Class="q_7635202.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="WindowRoot">
<ListView
SelectedItem="{Binding SelectedItem}"
ItemsSource="{Binding ItemModels}">
<ListView.View>
<GridView>
<GridViewColumn
DisplayMemberBinding="{Binding Name}"
Header="Name" />
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button
Content="Select"
Command="{Binding DataContext.SelectItem,
ElementName=WindowRoot}"
CommandParameter="{Binding}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Window>
Its all pretty straight forward. I'm leaving out the ICommand implementation as it is trivial.