ItemsControl in a list view - c#

I have some defined XAML that looks like
<ListView ItemsSource="{Binding ConfigItems,Mode=OneWay}" Margin="5">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
</Style>
</ListView.ItemContainerStyle>
<ItemsControl
ItemsSource="{Binding}"
ItemTemplateSelector="{StaticResource ConfigItemDataSelector}"/>
</ListView>
Now I realize that this isn't how it should be but that is the source of the question. As is I get an exception that indicates the ItemsSource should be empty before using it. From a question asked 12 years ago I see that this exception is raised when Items and ItemsSource properties are being used at the same time. So I set out to use one or the other. But I get this error when I define it like above or when I leave ItemsSource out altogether. When I try to use Items I get an error at compile time that indicates the list has to have a setter.
In case there is some confusion the ConfigItemDataSelector looks like basically returns a DataTemplate depending on which view model is being used. But I think looking at the selector is a distraction from the problem.
public sealed class ConfigItemDataSelector : DataTemplateSelector
{
public DataTemplate MeasurementDataTemplate { get; set; }
. . .
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is MeasurementConfigItemViewModel)
{
return MeasurementDataTemplate;
}
else if (item is LEDFeedbackConfigItemViewModel)
{
return LEDFeedbackDataTemplate;
}
. . .
return null;
}
}
So how should the ListView use the ItemsControl?

You can not put ItemsControl in ListView.Items
Try this
<ListView ItemsSource="{Binding ConfigItems,Mode=OneWay}" Margin="5">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding}"
ItemTemplateSelector="{StaticResource ConfigItemDataSelector}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
You can see source code in referencesource.microsoft.com
The ItemsControl have DefaultPropertyAttribute("Items"), so if you do not declare what property you used in xaml, it will set to ListView.Items be equal
<ListView ItemsSource="{Binding ConfigItems,Mode=OneWay}" Margin="5">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.Items>
<ItemsControl
ItemsSource="{Binding}"
ItemTemplateSelector="{StaticResource ConfigItemDataSelector}"/>
</ListView.Items>
</ListView>
And throw exception say "ItemsSource should be empty before using it"

I found through trial and error that the following works
<ListView Margin="5">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
</Style>
</ListView.ItemContainerStyle>
<ItemsControl
ItemsSource="{Binding ConfigItems}"
ItemTemplateSelector="{StaticResource ConfigItemDataSelector}"/>
</ListView>
Basically taking the ItemsSource property from the ListView and moving it to the ItemsControl.

Related

How to use customTabItem in TabControls in WPF

I am using TabControl with ItemSource as List. Based on the List, TabItems are getting generated.
<TabControl Width="Auto" VerticalAlignment="Top" ItemsSource="{Binding MyTabItems, Mode=TwoWay}" HorizontalAlignment="Stretch">
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}" BasedOn="{StaticResource TabItemStyle}" >
<Setter Property="FontSize" Value="10pt"/>
</Style>
</TabControl.Resources>
</TabControl>
I have created my own class MyTabItem inheriting from TabItem. I would like to use MyTabItem in this TabControl.
Can someone suggest a way to do it?
Use DataTemplate
<TabControl x:Name="tabCases" IsSynchronizedWithCurrentItem="True" Controls:ClosableTabItem.TabClose="TabClosed">
<TabControl.ItemTemplate>
<DataTemplate>
<Controls:MyTabItem >
<TextBlock Text="{Binding Path=Id}" />
</Controls:MyTabItem >
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
If you want to change the type of the containers from TabItem to your custom type, you should create a custom TabControl and override its GetContainerForItemOverride() method:
public class MyTabControl : TabControl
{
protected override DependencyObject GetContainerForItemOverride()
{
return new MyTabItem();
}
}
Of course you must also use this custom type instead of the built-in one in your XAML:
<local:MyTabControl Width="Auto" VerticalAlignment="Top" ItemsSource="{Binding MyTabItems, Mode=TwoWay}" HorizontalAlignment="Stretch">
<TabControl.Resources>
<Style TargetType="{x:Type local:MyTabItem}" BasedOn="{StaticResource TabItemStyle}" >
<Setter Property="FontSize" Value="10pt"/>
</Style>
</TabControl.Resources>
</local:MyTabControl>

How to remove rerendering during Windows UWP ScrollIntoView

When I use ScrollIntoView for my listview, The page goes blank for a moment and then rerenders, how can I make this more smooth?
Here is my current code:
<ListView Name="MessageList" Margin="0,82,0,45"
SelectionMode="None"
IsItemClickEnabled="True"
ItemClick="FileMessage_Click"
HorizontalAlignment="Stretch"
ItemTemplateSelector="{StaticResource MsgDataTemplateSelector}"
ItemsSource="{x:Bind messages}" >
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
And in the code-behind I have this each time a new message is added to the observable collection that feeds into the listview:
MessageList?.ScrollIntoView(MessageList.Items[MessageList.Items.Count -1]);
I didn't reproduced your problem completely. On my PC, this problem only occurs at the first time I add item to ListView. ListView use ItemsStackPanel as its default panel, which supports UI Virtualization. After changing the default panel to StackPanel which has no support for UI Virtualization, the problem doesn't appear.
You can change the ItemsPanel of ListView like below:
<ListView Name="MessageList" Margin="0,82,0,45"
SelectionMode="None"
Height="300"
IsItemClickEnabled="True"
HorizontalAlignment="Stretch"
ItemsSource="{x:Bind messages}" >
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel></StackPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
Update: To scroll to the last Item, you need to set the height of ListView and call ListView.UpdateLayout before ScrollIntoView:
MessageList.UpdateLayout();
MessageList.ScrollIntoView(MessageList.Items[MessageList.Items.Count - 1]);

Get the selected items of a ListView (MVVM, Caliburn.Micro)

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); }
}

Bind "IsEnabled" to ListView in Windows Phone 8.1

Ok, I've got a ListView. I've set ItemsSource from code. This ListView has Property SelectionMode="Multiple". But I'd like to disable some items from selecting.
XAML: //not working
<ListView x:Name="MyListView" SelectionMode="Multiple">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="IsEnabled" Value="{Binding Path=enabled, Mode=TwoWay}" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<!-- my template -->
</ListView.ItemTemplate>
</ListView>
Although, I cannot set each ListViewItem from code...
You can try using a Converter? See this easy tutorial.

Binding to a dependency property of a behavior inside a child control

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>

Categories

Resources