How to bind PreviewMouseDown to FormattedText within an ItemsControl - wpf

In WPF using MVVM I would like to set a property in the view model to the displayed text when the mouse is clicked. That is I want the PreviewMouseDown event from the ItemsControl to set a property in the viewmodel.
In the following XAML, I am using an ItemsControl to display Strings from a FormattedText ObservableCollection. All goes well with the XAML below to display the FormattedText.
But, how can I bind a PreviewMouseDown to each of the generated items for the view model?
All my attempts to use DataTemplate within the ItemsControl ultimately lead to:
System.Windows.Data Error: 26 : ItemTemplate and ItemTemplateSelector are ignored for items already of the ItemsControl's container type;
XAML
<ItemsControl
ItemsSource="{Binding Strings}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas
Background="Transparent"
Width="{x:Static h:Constants.widthCanvas}"
Height="{x:Static h:Constants.heightCanvas}"
/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Adding
h:MouseBehaviour.PreviewMouseDownCommand="{Binding PreviewMouseDown}"
to the Canvas definition never results in the command being called and I can't add it in a DataTemplate.
Any help or better idea is appreciated.

as items in an ItemsControl are hosted in ContentPresenter so if you bind your command to the same it will be applied to the Item's in the ItemsControl
so for that purpose we can use a generic Style for ContentPresenter in the resources of ItemsControl or any parent container
eg
<Style TargetType="ContentPresenter">
<Setter Property="h:MouseBehaviour.PreviewMouseDownCommand"
Value="{Binding PreviewMouseDown}" />
</Style>
above example is based on assumption that PreviewMouseDown command is in the view model for each item, if the command is in the parent view model then you may perhaps use
<Style TargetType="ContentPresenter">
<Setter Property="h:MouseBehaviour.PreviewMouseDownCommand"
Value="{Binding PreviewMouseDown, RelativeSource={RelativeSource FindAncestor,AncestorType=ItemsControl}}" />
</Style>

Related

Binding Itemscontrol to ObservableCollection: extra generated item

Binding ItemsControl to an ObservableCollection<T> places an extra {NewItemPlaceholder} in the control at runtime. How can I remove it?
N.B. I have seen posts related to this problem but those are limited to DataGrid where you can set CanUserAddRows or IsReadOnly properties to get rid of this item. ItemsControl doesn't have any such property.
XAML
Here's my ItemsControl (MyPoints is ObservableCollection<T> in the underlying ViewModel):
<ItemsControl ItemsSource="{Binding MyPoints}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="FrameworkElement">
<Setter Property="Canvas.Left" Value="{Binding Path=X}" />
<Setter Property="Canvas.Top" Value="{Binding Path=Y}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type vm:PointVM}">
<Ellipse Width="10" Height="10" Fill="#88FF2222" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This displays an extra point at 0,0. Live Property Explorer shows that this point is bound to the {NewItemPlaceholder} object.
MSDN writes the following:
When a CollectionView that implements IEditableCollectionView has
NewItemPlaceholderPosition set to AtBeginning or AtEnd, the
NewItemPlaceholder is added to the collection. The NewItemPlaceholder
always appears in the collection; it does not participate in grouping,
sorting, or filtering.
Link: MSDN
Hopefully if you set the NewItemPlaceholderPosition to something else, the placeholder will disappear from the collection.
Edit:
If your ObservableCollection<T> is being binded to somewhere else as well (e.g.: to a DataGrid, you have to set the CanUserAddRows to false), you have to deal with that other item. It would add a new NewItemPlaceholder to your collection.
Finally finally!
After spending a day, the solution turned out to be simple (although not ideal). ItemTemplateSelector allows me to select a template based on the item type, so I can create a simple selector to get rid of the extra item:
public class PointItemTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item == CollectionView.NewItemPlaceholder)
return (container as FrameworkElement).TryFindResource("EmptyDataTemplate") as DataTemplate;
else
return (container as FrameworkElement).TryFindResource("MyDataTemplate") as DataTemplate;
}
}
MyDataTemplate and EmptyDataTemplate should be defined in the Resources section of your ItemsControl. EmptyDataTemplate doesn't need to have any content.
Edit
#KAI's guess (see accepted answer) was correct. My ObservableCollection<T> was bound to a DataGrid which was causing this problem. I'll still keep my answer here as it provides a solution for other related situations.

Listbox "IsSelected" binding only partially working

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.

Get context in which a view is rendered?

In my WPF application I have a viewmodel class called CompanyViewModel.
Sometimes, an instance of this class is set as the DataContext of my main window, which is defined like this:
<window x:Class= ..... >
<Grid>
<ContentControl Content="{Binding }"></ContentControl>
</Grid>
</Window>
In this case I want a view to be used that displays all the properties of the viewmodel.
Other times, a ListView control has its itemsource set as a collection containing instances of CompanyViewModel. Here, I want a view to be used that renders only some important properties.
I have this in the resource dictionary of MainWindow.xaml:
<DataTemplate DataType="{x:Type vm:CompanyViewModel}">
<vw:CompanyView></vw:CompanyView>
</DataTemplate>
Is it possible to select a view for the viewmodel based on the context where the viewmodel is bound? For instance, to use CompanyView when displayed in the ContentControl of a window or when in a TabControl, and to use CompanyViewSmall where displayed in a ListView?
The DataTemplate to use is first looked for locally, and then looked for further up the Visual Tree hierarchy if it's not found.
Because of this, you can specify the DataTemplate to use further down the hierarchy to use something different than normal.
For example, the following will use the CompanyView anywhere the CompanyViewModel is in the visual tree, except in the specific ListView where the DataTemplate is specified as the smaller view.
<Window.Resources>
<DataTemplate DataType="{x:Type vm:CompanyViewModel}">
<vw:CompanyView />
</DataTemplate>
</Window.Resources>
<ListView>
<ListView.Resources>
<DataTemplate DataType="{x:Type vm:CompanyViewModel}">
<vw:CompanyViewSmall />
</DataTemplate>
</ListView.Resources>
</ListView>
You could also use an implicit style for the ListView telling it to use the smaller template in the .Resources, however this will apply the smaller view to any ListView, not just specific ones, and if you ever apply another style to a ListView you'll have to remember to inherit the default style to keep the smaller DataTemplate.
<Style TargetType="{x:Type ListView}">
<Style.Resources>
<DataTemplate DataType="{x:Type vm:CompanyViewModel}">
<vw:CompanyViewSmall />
</DataTemplate>
</Style.Resources>
</Style>

WPF : TreeView virtualization not working

What can stop a TreeView from virtualizing if the TreeView is set up as follows?
<TreeView
ItemsSource="{Binding}"
VirtualizingStackPanel.IsVirtualizing="True">
<TreeView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</TreeView.ItemsPanel>
<TreeView.ItemContainerStyle>
<Style
TargetType="{x:Type TreeViewItem}">
<Setter
Property="IsExpanded"
Value="{Binding IsExpanded, Mode=TwoWay}"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
I have one that is not virtualizing, when i expand the nodes (and use snoop to check) i have all of the TreeViewItems being created. I am wondering if there is some combination of containers that would prevent the TreeView from virtualizing its content. (like hosting it in a StackPanel for example)
The problem was with the styling. After some research we found that there was an unnamed style targeting the TreeView (i.e. one with DataType={x:Type TreeView} without an x:Key) and one targetting the TreeViewItem in our App.xaml (or equivalent) It was overriding the ControlTemplate for each respectively.
These styles did not have the triggers to set the ItemsPanel to a VirtualizingStackPanel and had no mention of any virtualization. When the styles are removed the TreeView works fine. Even though the local properties set the ItemsPanel and the VirtualizingStackPanel.Isvirtualizing="True" on the TreeView these properties were not being propogated to the TreeViewItems so the top level of the TreeView would virtualize whilst the sub categories would not (as their virtualization behaviour was dependant on the TreeViewItem)
Had the same problem. In my case, the initial size of the TreeView was not limited (TreeView is within a popup). Therfore, the virtualization panel initialized all controls for the first time.
Setting the MaxHeigt of the TreeView to 1000 solvend the problem.

Silverlight - Add Ellipses to a canvas dynamically with MVVM

I want to add a dynamic number of ellipses to a canvas and set the ellipse position (canvas.top, canvas.left). I tried binding to an ItemsControl but it each item (ellipse) has a container, so I cant set the ellipses position directly. I don't want an items container, I just want a canvas that contains ellipses.
Can this be done?
Try this - worked for me -- I use it to freely place textblocks on a canvas.
Re: Re: Positioning Items when Canvas is the ItemsPanel of an ItemsControl
02-26-2010 7:17 AM |
There is an alternative simpler solution that does work in silverlight 3.
<Canvas>
<ItemsControl ItemsSource={Binding MyItems}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas>
<TextBlock Canvas.Left={Binding Left} Canvas.Top={Binding Top} Text={Binding Text} />
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
If the MyItems is a list of items that are of a class that has Left, Top and Text public properties, this works fine. I have also tested with Line and Border to draw simple bar graph graphics in silverlight 3.
From the bottom of this post:
http://forums.silverlight.net/forums/p/29753/450510.aspx#450510
Combine it with a Silverlight DataTemplateSelector and you can change the objects you draw based on view model properties:
http://www.codeproject.com/KB/silverlight/SLTemplateSelector.aspx
Ordinarily I would say use an ItemsControl in conjunction with a Canvas:
<ItemsControl ItemsSource="{Binding Ellipses}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemsContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding Left}"/>
<Setter Property="Canvas.Top" Value="{Binding Top}"/>
</Style>
</ItemsControl.ItemsContainerStyle>
</ItemsControl>
But in a display of Silverlight suckiness, the ItemContainerStyle property does not work on ItemsControl. It has support in ItemsControl, but it's not exposed by ItemsControl itself. Instead, it's up to subclasses of ItemsControl - such as ListBox - to expose it. Oh, and those subclasses have to be provided by Microsoft because the functionality is protected internal, so you can't just subclass ItemsControl and expose this stuff yourself. :S
So you could use ListBox instead, possibly by subclassing it and changing its item container to something simpler than a ListBoxItem. Or you could just use ListBox directly and fiddle around until the ListBoxItems look the way you want them to (i.e. not selected).

Resources