Handle Multiselect in ListView (WPF + MVVM) [duplicate] - c#

I have a ListBox that I populate dynamically via a binding (this is defined in a DataTemplate, which is why the binding is somewhat unusual):
<ListBox SelectionMode="Extended" ItemsSource="{Binding DataContext.ResultList, RelativeSource={RelativeSource AncestorType=Window}}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Object}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Each ListBoxItem's IsSelected property is bound to an IsSelected property on a custom object.
When I select individual ListBoxItems, the binding works properly - the custom object's IsSelected property is updated in my ViewModel. However, if I select all of the ListBoxItems with a Ctrl+A command, only the currently visible ListBoxItems (those that are currently in my scrolling viewport) update their ViewModel bindings. On the frontend, all the ListBoxItems appear to be selected, and the ListBox.SelectedItems.Count property on the container ListBox shows that all items are selected.
Furthermore, as I scroll through the ListBox after selecting all ListBoxItems with Ctrl+A, the bindings are successfully updated when each ListBoxItem is scrolled into view.
Why does this binding seem to be only partially working? Is there a better way to handle the binding of the IsSelected property when large numbers of ListBoxItems can be selected simultaneously?
Edit:
This behavior doesn't happen exclusively with the Ctrl+A command - I get the same results when selecting all the items using a shift+click.

I think the behavior you're seeing is to due to VirtualizingStackPanel.IsVirtualizing which is True by default when binding to ItemsSource of ListBox
if you for eg set your ListBox such as:
<ListBox VirtualizingStackPanel.IsVirtualizing="False" SelectionMode="Extended" ItemsSource="{Binding DataContext.ResultList, RelativeSource={RelativeSource AncestorType=Window}}">
or
<ListBox ...>
...
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
then you should see all your bound items have their IsSelected updated accordingly with Ctrl+A or Shift + ...
Properties such as Count of the collection even with virtualization would report the correct value to accommodate for things like computing the required ScrollBar.Height. Items which are outside the View-port do not get rendered hence no bindings are in effect on them until they actually get used.

Related

TabControl - Display N TabItems depending on List<List<T>> dimension [duplicate]

If I have a class called: GuiObject, and that class has a list of GuiObjects called: "GuiObjects".
Now say my window has a list of GuiObjects, which I use in the .xaml file to dataBind to:
<StackPanel>
<ItemsControl ItemsSource="{Binding TopObjectList}" DataTemplateSelector="{DynamicResource templateSelector"/>
</StackPanel>
I can make a datatemplate for every type of FrameworkElement I want to generate, but I'm having trouble with the TabControl. I can create a datatemplate for the tabControl like so:
<DataTemplate x:key="TabControlTemplate" DataTemplateSelector="{DynamicResource templateSelector" >
<TabControl ItemsSource="{Binding GuiObjects}" />
</DataTemplate>
And the result is a tab control that has each of the proper pages present, but without the contents of the individual TabItems. Fair enough, I'll just make a DataTemplate for the TabItems. For each TabItem, I'd like to put the contents of GuiObjects into a stackpanel.
<DataTemplate x:key="TabItemTemplate" DataTemplateSelector="{Resource templateSelector">
<TabItem Header = {Binding Title}>
<StackPanel>
<ItemsControl ItemsSource="{Binding GuiObjects}" DataTemplateSelector="{DynamicResource templateSelector"/>
</StackPanel>
</TabItem>
</DataTemplate>
The problem here is that the TabItemTemplate never gets called. I've tried solutions that involve setting the ItemContainerStyle within the TabControlTemplate, but then I've got the problem of hierarchy. If I bind "GuiObjects" inside the content of the TabItem, I'm binding the list of tabItems, instead of the list that's within each TabItem. (I want to do the second one). Here's an example:
<DataTemplate x:key="TabControlTemplate" DataTemplateSelector="{DynamicResource templateSelector" >
<TabControl ItemsSource="{Binding GuiObjects}">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Title}"/>
<Setter Property="Content" Value="<StackPanel><ItemsControl ItemsSource="{Binding GuiObjects}" DataTemplateSelector="{DynamicResource templateSelector"/></StackPanel>"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
</DataTemplate>
Again, this solution has the levels problem: When I say: {Binding GuiObjets} I'm referring to the list of TabItems, instead of to the list of FrameworkElements within each TabItem.
The solution is either to stick with separate DataTemplates for both the TabControl and the TabItem, and just fix it so that the DataTemplateSelector actually works for the TabItems (no idea how to do this). Or to go with the ItemContainerStyle, and somehow tell it to go down one level when binding GuiObjects. Anyone know how to do this?
To provide a template for the contents of the pages of a TabControl, use the following properties:
ContentTemplate
ContentTemplateSelector
The ItemTemplate/ItemTemplateSelector properties of a TabControl are used to define what the tab headers look like.

WPF ComboBox doesn't initially select correct item

I have a WPF ComboBox that I bind to a list of custom objects, each of which contains an IsSelected property. This property is bound to its corresponding ComboBoxItem's IsSelected property. When initially creating the list, I set a particular object's IsSelected property to true, with the expectation that this item will be selected when the ComboBox is initially shown. However, the ComboBox always shows the first item in the list as selected, regardless of which object in its bound list has its IsSelected property set to true.
I know that the binding is working properly, because when I click the ComboBox and it expands to show all the available options, the ComboBox updates to show the correct selected item.
Here's the XAML the defines my ComboBox. It's in a DataTemplate, and is dynamically added/removed from the page, if that is at all relevant:
<ComboBox ItemsSource="{Binding DataContext.YearList, RelativeSource={RelativeSource AncestorType=Window}}" IsSynchronizedWithCurrentItem="True">
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ComboBox.ItemContainerStyle>
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Object}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Try removing IsSynchronizedWithCurrentItem="True". That setting is telling the control to use the CurrentItem on the bound collection's generated ICollectionView, which defaults to the first item in the list. If you want to see what its doing you can play with this view in code by using CollectionViewSource.GetDefaultView(YearList) and then looking at or changing the current item.
Try binding to SelectedItem property, ComboBox will not select first item by default but your Source of data binding:
<ComboBox ItemsSource="{Binding YearList, RelativeSource={RelativeSource AncestorType=Window}}" SelectedItem="{Binding <YourDefinedSelectedItemProperty>, RelativeSource={RelativeSource AncestorType=Window}}">
Hope this helps.

Setting Canvas Children Property without ItemsControl ItemsSource Binding Property

Is there any means to set Canvas Children Property without ItemsControl ItemsSource Binding Property?
In order to keep separated my view from viewmodel, I have to bind the items.
I have used the the canvas as a designer from 'CodeProject'
http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part2.aspx
I'm using a canvas for drag-and-drop purposes. It works well when I work manually inside the canvas.
Which means I add and remove the child items using
myCanvas.Children.Add(userControl);
myCanvas.Children.Remove(userControl);
But if I load my usercontrols at run time, they are loaded just as views.
<s:Canvas AllowDrop="True" >
<ItemsControl Grid.Row="1" ItemsSource="{Binding Path=userControls}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<s:Canvas Background="Transparent"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<s:ControlItem Content="{Binding Path=MyView}"></s:ControlItem >
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding Path=X}" />
<Setter Property="Canvas.Top" Value="{Binding Path=Y}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</s:Canvas>
No, there aint. (Except manually clearing and adding...)
Ummm yeah just draw items inside the canvas? :)
<Canvas>
<TextBlock Text="I'm Child #1" />
<TextBlock Text="I'm Child #2" Canvas.Top="50" />
</Canvas>
Or you can always do it in code-behind
myCanvas.Children.Add(myTextBlock);
foreach(var someControl in SomeControlList)
myCanvas.Children.Add(someControl);
Edit
I see your update and have no idea what you're asking. If you want to drag/drop items onto a Canvas, you are better off adding/removing items from the ItemsSource than manually adding/removing items from the Canvas. Simply adding/removing them from myCanvas will not update the collection in your ItemsSource
I would recommend taking a look at Bea Stollnitz's article on dragging/dropping databound Items. This means you would keep the ItemsControl you have now, but when you drop an item on top of the Canvas it adds the DataItem behind that object to the ObservableCollection<MyDataItem> that you call userControls (I don't like this name because it suggests that the data items contain UI items, which should not be the case)

WPF ListBox in Popup setting null SelectedItem on PopupClose

I have a WPF Popup, which is structured as below (with some senstitive stuff removed)...
<Popup>
<Border>
<StackPanel>
<ListBox
ItemSource="{Binding X}"
SelectedItem="{Binding Y}"
IsSynchronizedWithCurrentItem="True"/>
<Separator/>
<MenuItem Command="{Binding Path=EditModeCommand}"/>
</StackPanel>
</Border>
</Popup>
The ListBox works as expected, the list populates from the binding, and the selected item feeds back to the collection correctly.
However when the MenuItem fires its command, the SelectedItem binding fires as well, setting the SelectedItem to null. Is there a way to preserve the SelectedItem when the listbox is not the focus of the click?
Try setting the SelectedItem property before the ItemSource property in the declaration.

How can I use data binding in WPF to create a new user control for each element in a list?

I have a list of objects. For each item in the list, I want to create a new user control bound to that item. From what I've read, doing this programmatically is bad practice with WPF (as well as less than straightforward), so I should use data binding as a solution instead. The problem is, I can't figure out how to do this. I don't know the contents of the list (just the type) at compile-time, so I can't create and bind with XAML for each element. Google and MSDN don't seem to have any answers, so maybe I'm thinking about this the wrong way? What do I need to do?
Thanks
EDIT: To clarify, I'm trying to make my own music scoring software, something like Rosegarden. The list would contain all of the measures, and the usercontrols would be their visual representation.
A more generic approach than Julien Lebosquain's suggestion (and one that will work when the list of items contains objects of more than one data type):
Create a DataTemplate to be used in presenting an item of the type(s) in your list, e.g.:
<DataTemplate DataType="local:Measure">
<local:MeasureUserControl DataContext="{Binding}"/>
</DataTemplate>
Use an ItemsControl to present the items:
<ItemsControl ItemsSource="{Binding MeasureList}"/>
You can set the ItemsPanel property of the ItemsControl to an ItemsPanelTemplate to govern how it will lay out the user controls, e.g.:
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
This approach is generally preferable to using a ListBox when you don't want the features of the ListBox, e.g. its default border and selection behavior.
You can use a standard ListBox with a custom item style:
Somewhere in the resources:
<Style TargetType="{x:Type ListBoxItem}" x:Key="CustomItemStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<yourns:YourControl />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
In your window/page/usercontrol:
<ListBox ItemsSource="{Binding ...}" ItemContainerStyle="{StaticResource CustomItemStyle}" />
Since your objects will be bound to the listbox, an implicit ListBoxItem will be created for each object, with its DataContext set to the object so you can use bindings in YourControl without any worries.
All the above answers work, but I'll leave with how I'm doing this in my application.
I'm implementing the MVVM architecture that takes advantage of these WPF features.
This is a UserControl I'm using that has an ItemsControl populated with the items of a certain type:
<UserControl x:Class="Controls.StepView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:Controls"
Background="Transparent">
<UserControl.Resources>
<DataTemplate DataType="{x:Type my:ParameterViewModel}" >
<my:ParameterView HorizontalAlignment="Stretch" Margin="25 0 0 0"/>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ItemsControl Name="stkStepContent" ItemsSource="{Binding Parameters}" />
</Grid>
</UserControl>
Let me explain the code for you. in the DataTemplate section I say that I want to render objects of class ParameterViewModel with the UserControl ParameterView. The ItemsSource property of my ItemsControl is binded to a List<ParameterViewModel>. When the ItemsControl is initiated for each ParameterViewModel on the List it will create a ParameterView and set its DataContext to the ParameterViewmodel it is rendering.
I found that this architectural pattern is the most intuitive for me to build WPF applications.

Categories