What is the situation:
I'm working on a C# WPF application with Caliburn.Micro. I am using the MVVM pattern.
I have a ListView with a ContentControl as ItemTemplate. The ListView's ItemsSource is bound to a List (ObservableCollection) of ViewModels in the corresponding ViewModel.
<ListView ItemsSource="{Binding ViewModelList}" SelectionMode="Extended">
<ListView.ItemTemplate>
<DataTemplate>
<ContentControl cal:View.Model="{Binding}" />
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
What I want:
I want to get the selected items (all of them) of the ListView, but I don't know how without violating the MVVM pattern.
It would be nice to have a property "IsSelected" in the ViewModels that are presented by the ContentControl and to bind that somehow to my ListView.
Is it possible to do that or is there another/a better way?
Update:
It was easier than expected. I added a property public bool IsSelected { get; set; } in my ViewModel and put this inside the ListView Control:
<UserControl.Resources>
<Style TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource {x:Type ListViewItem}}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</UserControl.Resources>
Now I can get the selected items with:
foreach (var item in ViewModelList)
{
if (item.IsSelected)
{
// Do stuff
}
}
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=OneWayToSource}"/>
</Style>
</Grid.Resources>
<ListView ItemsSource="{Binding ViewModelList}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding IsSelected}"/>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal">
</StackPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
</Grid>
This is probably the code you're looking for. If you're using a multiple selection list view, you could get all the selected items with below code.
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SelectionChanged}" CommandParameter="{Binding Path=SelectedItems, ElementName=ListViewName}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Bind it to a command in the viewmodel.
private ICommand selectionChanged;
public ICommand SelectionChanged
{
get { return selectionChanged; }
set { SetProperty(ref selectionChanged, value); }
}
Related
I want to create a Listbox from where it's possible to select elements with checkboxes. It gets the elements through data-binding from a database. The items appear in the Listbox, but when I send the form, the code-behind doesn't receive a SelectedItem value.
The XAML section for the listbox looks like this:
<Grid x:Name="grMozik" Visibility="Visible" Margin="0,0,0,0" DataContext="{Binding}" Grid.Row="3" Grid.ColumnSpan="2">
<ListBox Name="lbMozik" Margin="15" Width="300" Height="200" ItemsSource="{Binding}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Focusable" Value="False"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding MoziNeve}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
And for test purposes the code that would show the selected item looks like this:
string text = ((ListBoxItem)lbMozik.SelectedItem).Content.ToString();
MessageBox.Show(text2);
ListBox.SelectedItem comes from a ListBoxItem which has IsSelected property set to true. If you want to select by CheckBoxes, then bind each of them to owner ListBoxItem.IsSelected:
<CheckBox Content="{Binding MoziNeve}"
IsChecked="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}"/>
I am implementing a listbox using MVVM approach and I'm having an issue where my double click command is not being triggered. When I launch my application I see my ListBox populated via binding just fine. But when I double click on an item nothing happens. Is there something I'm missing? Many thanks in advance.
Here is how I have my UserControl (xaml) set up
<ListBox
x:Name="Files"
ItemsSource="{Binding FileCollection, Mode=TwoWay}"
SelectedItem="{Binding Filename, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}">
<TextBlock.InputBindings>
<MouseBinding
Gesture="LeftDoubleClick"
Command="{Binding EditFileCommand}"/>
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This is how I am setting up my command object in my View Model:
//..using system.windows.input for ICommand
private ICommand editFileCommand = null;
public ICommand EditFileCommand
{
get
{
//RelayCommand comes from GalaSoft.MvvmLight.Command
return editFileCommand ?? new RelayCommand(EditFile);
}
}
private void EditFile()
{
MessageBox.Show("Double Click!");
}
This is almost similar to RelayCommand, you can use it like this:
Declare in your ViewModel:
public RelayCommand EditFileCommand { get; set; }
Then, you need to initialize it:
EditFileCommand = new RelayCommand(EditFile);
The XAML remains equal:
<ListBox
x:Name="Files"
ItemsSource="{Binding FileCollection, Mode=TwoWay}"
SelectedItem="{Binding Filename, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}">
<TextBlock.InputBindings>
<MouseBinding
Gesture="LeftDoubleClick"
Command="{Binding EditFileCommand}"/>
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
If the command property is defined in the File class, it should work provided that you actually click on the TextBlock. You could make the TextBlock stretch across the ListBoxItem container by using an ItemContainerStyle:
<ListBox x:Name="Files" ...>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
...
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
If the command is defined in the view model where the FileCollection property is defined, you should use a RelativeSource:
<TextBlock.InputBindings>
<MouseBinding Gesture="LeftDoubleClick"
Command="{Binding DataContext.EditFileCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"/>
</TextBlock.InputBindings>
I am working on WPF application using MVVM. I have two page. I have multiple UserControls in a page 1, on selection of UserControls from page 1, I want to show that selected userControl in 2nd page. Below are my code.
ViewModel Code
public RelayCommand<string> OnClickSelectWidgetCommand => new RelayCommand<string>((setUserControlName) =>
{
using (new CursorWait())
{
var MyContentControl = setUserControlName;
MessageBox.Show(MyContentControl);
//How to render UserControl to View?
}
}, true);
Here in above code I get the UserControl name in setUserControlName variable. Now how to bind that UserControl to XAML page? Below are my code that I have tried.
View Code
<StackPanel Background="Black" VerticalAlignment="Top">
<Border Name="UserControl1BorderLow" BorderBrush="White" BorderThickness="0" >
<ItemsControl ItemsSource="{Binding LowCollection}" Margin="4,0" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel HorizontalAlignment="Left" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:UserControlColumn1XL HorizontalAlignment="Left" Margin="2" />
<!--what can I do here in above line to make it dynamically render the userControl in place of UserControlColumn1XL-->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border></StackPanel>
Above code, In DataTemplate what need to be change to bind UserControls dynamically?
There are two ways to solve this, one involves setting the template based on your data type (DataTemplates) and the second involves setting it based on the data itself (DataTriggers).
In the first case your LowCollection should be an array of objects, or some base class that your view models are all derived from (ViewModel1, ViewModel2 etc). In this case you can get rid of your itemtemplate altogether and just add DataTemplates to specify how each of the items in your ItemsControl should be represented:
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type local:ViewModel1}">
<UserControl1 />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModel2}">
<UserControl2 />
</DataTemplate>
... etc...
In the second case you need to set a template based on the value of some property in your view model. In this case you do need to set the ItemTemplate, and you give it a Style which uses data triggers to set an appropriate DataTemplate:
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding}">
<ContentPresenter.Style>
<Style TargetType="{x:Type ContentPresenter}">
<Style.Triggers>
<DataTrigger Binding="{Binding YourProperty}" Value="YourValue1">
<Setter Property="ContentTemplate" Value="{StaticResource YourDataTemplate1}" />
</DataTrigger>
<DataTrigger Binding="{Binding YourProperty}" Value="YourValue2">
<Setter Property="ContentTemplate" Value="{StaticResource YourDataTemplate2}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentPresenter.Style>
</ContentPresenter>
</DataTemplate>
</ItemsControl.ItemTemplate>
The relevant parts to note here are that there is a property in your view model called YourProperty which can have two values i.e. YourValue1 or YourValue2; the style above then selects either YourDataTemplate1 or YourDataTemplate2, depending on the value of YourProperty.
While reading about the datatemplates I noticed you can choose different templates based on the type of data. - However can one also do this for different content of the data?
My modelview provides a list of data, in principle it's just a list of tuples (bound in a custom class to make typing easier) with Tuple<ImageData, AltText>.
The type of the property in the ModelView is:
ReadOnlyObservableCollection<ThumbDispData>
With ThumbDispData:
public class ThumbDispData
{
public ImageData Idhl { get; set; }
public string AltText { get; set; }
}
Now I wish to display an Image if it can (ImageData.Source is non null) - Otherwise it should show an alt-text.
The xaml or the usercontrol:
<UserControl x:Class="test.ThumbPanel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:test="clr-namespace:test"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<DataTemplate DataType="{x:Type test:ThumbDispData}">
<TextBlock Text="{Binding AltText}"></TextBlock>
</DataTemplate>
</UserControl.Resources>
<Grid Background="Transparent">
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</UserControl>
Well above only shows the alt-text (which works): how would I create a selector based on the content of ThumbDispData.
You could use a DataTemplateSelector:
<UserControl.Resources>
<DataTemplate x:Key="tt" DataType="{x:Type test:ThumbDispData}">
<TextBlock Text="{Binding AltText}"></TextBlock>
</DataTemplate>
<DataTemplate x:Key="img" DataType="{x:Type test:ThumbDispData}">
<Image Source="{Binding Idhl}" />
</DataTemplate>
<local:Selector x:Key="selector" TextTemplate="{StaticResource tt}" ImageTemplate="{StaticResource img}" />
</UserControl.Resources>
<Grid Background="Transparent">
<ItemsControl ItemsSource="{Binding}" ItemTemplateSelector="{StaticResource selector}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
public class Selector : DataTemplateSelector
{
public DataTemplate ImageTemplate { get; set; }
public DataTemplate TextTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
ThumbDispData data = item as ThumbDispData;
if (data != null && data.Idhl != null)
return ImageTemplate;
return TextTemplate;
}
}
would I create a selector based on the content of ThumbDispData.
You would typically write a subclass of DataTemplateSelector, in which you check the Idhl property and return a different template object depending on whether it's null or not. Typically, the template would be a resource, which you load dynamically in the selector.
But your scenario is simple enough that I would use styles instead. For example:
<DataTemplate DataType="{x:Type test:ThumbDispData}"
xmlns:p="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<Grid>
<TextBlock Text="{Binding AltText}">
<TextBlock.Style>
<p:Style TargetType="TextBlock">
<Setter Visibility="Visible"/>
<p:Style.Triggers>
<DataTrigger Binding="{Binding Idhl}" Value="{x:Null}">
<Setter Visibility="Collapsed"/>
</DataTrigger>
</p:Style>
</p:Style>
</TextBlock.Style>
</TextBlock>
<Image Source="{Binding Idhl}">
<Image.Style>
<p:Style TargetType="Image">
<Setter Visibility="Collapsed"/>
<p:Style.Triggers>
<DataTrigger Binding="{Binding Idhl}" Value="{x:Null}">
<Setter Visibility="Visible"/>
</DataTrigger>
</p:Style>
</p:Style>
</Image.Style>
</Image>
</Grid>
</DataTemplate>
Notes:
Strictly speaking, you don't need the <Setter Visibility="Visible"/> in the TextBlock style, since that's the default value. I put it there because it helps document the symmetry between the styles of the two elements.
The xmlns:p declaration is there only as a Stack Overflow work-around, because the XML formatter doesn't handle the XAML Style element correctly otherwise. In real XAML, you can leave that out and just use the Style name directly instead of p:Style.
I have the following user control with a custom dependency property
ThumbnailListView UserControl
<ListView
ItemsSource="{Binding}"
BorderThickness="0,0,0,0"
HorizontalAlignment="Center"
Background="White"
SelectionChanged="ListView_SelectionChanged"
AllowDrop="True">
<i:Interaction.Behaviors>
<behaviors:DragDropBehavior OnDragDrop="{Binding Path=ItemDragDrop}"></behaviors:DragDropBehavior>
</i:Interaction.Behaviors>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource IsLastListItem}}" Value="False">
<Setter Property="Margin" Value="0,0,0,20"></Setter>
</DataTrigger>
</Style.Triggers>
<Setter Property="Background" Value="Gray"></Setter>
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"></Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Image Source="{Binding Thumbnail, Converter={StaticResource ImageConverter}}"></Image>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Dependency Property ItemDragDrop of ThumbnailListView
public static ICommand GetItemDragDrop(DependencyObject obj)
{
return (ICommand)obj.GetValue(ItemDragDropProperty);
}
public static void SetItemDragDrop(DependencyObject obj, ICommand value)
{
obj.SetValue(ItemDragDropProperty, value);
}
public static DependencyProperty ItemDragDropProperty = DependencyProperty.RegisterAttached("ItemDragDrop",
typeof(ICommand), typeof(ThumbnailListView));
public ICommand ItemDragDrop
{
get
{
return (ICommand)GetValue(ItemDragDropProperty);
}
set
{
SetValue(ItemDragDropProperty, value);
}
}
NewScansView UserControl
<DockPanel Dock="Top">
<ListView ItemsSource="{Binding Scans}" Width="500">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Caption}" Margin="5,0,0,0"></TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
<Views:ThumbnailListView DataContext="{Binding SelectedItem.Pages}" ItemDragDrop="{Binding SelectedItem.DragDropCommand}" VerticalAlignment="Stretch" Width="200" />
<Views:PageListView DataContext="{Binding SelectedItem.Pages}" VerticalAlignment="Stretch" />
</DockPanel>
The NewScansView xaml contains a ThumbnailListView control and has bound the dependency property ItemDragDrop of the ThumbnailListView to a command in the class of SelectedItem.
Inside the ThumbnailListView user control I have a behavior DragDropBehavior which has a dependency property OnDragDrop.
I am trying to bind OnDragDrop to ItemDragDrop so that when a drag drop operation completes the command in the SelectedItem class is executed.
The problem is that it doesn't seem to be able to find either the ItemDragDrop property on the ThumbnailListView or the DragDropCommand of the selected item class.
I'm wondering what i'm doing wrong and how I can set it up?
ThumbnailListView.DataContext is set to SelectedItem.Pages, and I highly doubt that SelectedItem.Pages has a property called SelectedItem.DragDropCommand.
Change the Source of your ItemDragDrop binding to specify it uses something other than the ThumnailListView.DataContext for the source of that binding.
<DockPanel x:Name="MyDockPanel" Dock="Top">
...
<Views:ThumbnailListView DataContext="{Binding SelectedItem.Pages}"
ItemDragDrop="{Binding SelectedItem.DragDropCommand, ElementName=MyDockPanel}" ... />
...
</DockPanel>
You probably also have to do the same for your behavior binding - change the source of it so it points to the ThumbnailListView instead of to the ThumbnailListView.DataContext
<i:Interaction.Behaviors>
<behaviors:DragDropBehavior OnDragDrop="{Binding Path=ItemDragDrop, RelativeSource={RelativeSource AncestorType={x:Type local:ThumbnailListView}}}" />
</i:Interaction.Behaviors>
Or better yet, either make a DependencyProperty for Pages so you don't rely on a specific DataContext object type
<Views:ThumbnailListView PagesDependencyProperty="{Binding SelectedItem.Pages}" ItemDragDrop="{Binding SelectedItem.DragDropCommand}" .. />
Or edit your control so it assumes a specific type is being used for the DataContext, and use an implicit DataTemplate to always draw that type of object using this control (far more common for me) :
<ListView ItemsSource="{Binding Pages}" ...>
<i:Interaction.Behaviors>
<behaviors:DragDropBehavior OnDragDrop="{Binding DragDropCommand}" />
</i:Interaction.Behaviors>
...
</ListView>
Implicit DataTemplate:
<DataTemplate DataType="{x:Type local:WhateverSelectedItemDataTypeIs}}">
<!-- DataContext will automatically set to WhateverSelectedItemDataTypeIs -->
<Views:ThumbnailListView />
</DataTemplate>