Binding Itemscontrol to ObservableCollection: extra generated item - wpf

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.

Related

WPF how to bind to the ICommand.CanExecute()

Basicaly I am facing very similar problem to the question WPF - how to hide menu item if command's CanExecute is false?, the accepted answer uses a clever workaround to bind to the IsEnabled instead of CanExecute() result. Alas IsEnabled workaround would not work in my case:
I have a collection of KeyBindings gathered from the VisualTree during the PreviewGotKeyboardFocus event from various controls, because of this I can have no assumption whether the original keybinding's element is disabled for some other reason (e.g. IsBeingLoaded binding) or even if the element is not disabled at all (e.g. Grid does not disable if cannot execute its KeyBindings). How can I bind to the KeyBinding.Command's current CanExecute() value?
public class ContextHelperVM : ViewModelBase
{
public ObservableCollection<KeyBinding> ContextEffectiveKeybindings { get; }
// KeyBinding list loading, maintaining, etc. pseudocode
// Keybindings = FocusedElement.TraverseToParentWidnow().GatherKeyBindings();
}
in the ContextHelperView.xaml I would like to set Opacity to non-executable KeyBindings
<ItemsControl ItemsSource="{Binding ContextEffectiveKeybindings}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Key}"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Command.????}" Value="False"><!--how to bind here??-->
<Setter Property="Opacity" Value="0.5">
<DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
How can I fix the code so that the Opacity is correctly set for KeyBinding.Commands that cannot be executed?

How to bind PreviewMouseDown to FormattedText within an ItemsControl

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>

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.

How to bind to Tabcontrol.Items

I have a WPF application that I'm trying to dynamically add items to a tabcontrol. I have a list of menu items that should be databound to the tabcontrol's items. The only problem is that TabControl.Items does not notify others that items have been added. I've tested this by binding instead to TabControl.Items.Count and get calls to the converter (but the value passed in is the count and not something useful). Here's the relevent code that doesn't get databound properly because Items doesn't call out updates:
<MenuItem ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabControl}}, Path=Items, Converter={StaticResource TabControlItemConverter}}">
This MenuItem XAML is inside a ControlTemplate for a TabControl. With static items, i.e., items that are already defined in a TabControl, this code works perfectly. But I have a TabControl that gets items added at runtime and can't seem to update this binding. Has anyone added some sort of attached property to a TabControl that can bind to the Items collection?
Edit for background info
The TabControl that has items added to it is a region (this is a Prism application). Here is the relevent XAML
<TabControl cal:RegionManager.RegionName="{x:Static local:LocalRegionNames.SelectedItemRegion}" >
<TabControl.Resources>
<Style TargetType="TabItem" BasedOn="{StaticResource TabItemStyle}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Style="{StaticResource tabItemImage}" Height="20" />
<TextBlock Text="{Binding Content.DataContext.TabHeader, RelativeSource={RelativeSource AncestorType=TabItem}}" VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
</TabControl>
The relevent code for adding a view to the region is here:
ProjectDetailView view = new ProjectDetailView();
ProjectDetailViewModel viewModel = new ProjectDetailViewModel();
viewModel.CurrentProject = project;
view.DataContext = viewModel;
IRegionManager retManager = RegionManager.Regions[LocalRegionNames.SelectedItemRegion].Add(view, null, true);
RegionManager.Regions[LocalRegionNames.SelectedItemRegion].Activate(view);
All this works fine...views get added, the tab control adds items, and views appear. But the Items property on the tabcontrol never broadcasts the changes to its collection.
You do the same thing for TabControls, you bind the ItemsSource, the only thing you need to take into account is that the source collection should implement INotifyCollectionChanged if you want it updated if items are added. ObservableCollection<T> already implements the interface and is often used as source for such bindings.

Strange behaviour in the WPF DataTemplate and property.

I have a DataTemplate that displays buttons in a StackPanel. Whenever a user clicks a button, the button is supposed to glow. So I have written the necessary DataTrigger in the Template and a Boolean condition in the property that I'm binding too. Here are the details below:
<DataTemplate x:Key="ActionItemsTemplate" DataType="ActionItemViewModel">
<ItemsControl IsTabStop="False" ItemsSource="{Binding}" Margin="6,2">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button x:Name="ActionButton" Command="{Binding Path=Command }" Content="{Binding Path=DisplayName}" Style="{DynamicResource HeaderButton}"/>
<!-- Set special values for Selected Item -->
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter TargetName="ActionButton" Property="Style" Value="{DynamicResource MainWindowSelectedButton}"/>
<!--Command="{Binding Path=Command}" Content="{Binding Path=DisplayName}"-->
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
I have implemented the INotifyPropertyChanged interface and the Property ActionItems returns an ObservableCollection.
The Problem: When I change the ObservableCollection and call the INotifyPropertyChanged event, it is not reflected in the DataTemplate directly, but only altering the property. However if I re-assgn the entire object with itself, it works perfectly.
e.g
void Test1()
{
_commands[0].IsSelected = !_commands[0].IsSelected;
_commands[0] = _commands[0]; // Does not work If this line is commented out
ActionItems = _commands;
}
What could the problem be?
Edit: I see that the problem could be with the DataBinding in this case.
I have arrived at a similar problem now where I have bound the IsExpanded property of the Expander control to a bool property inside a TabPanel. When I toggle the bool property, the value is changed behind, but not reflected in the display. However suppose I change tabs and come back, I see the change has taken place. Is this related to this problem here?
And again I wonder what the problem could be (narrowed it down a bit :))
Edit 2: Solution for second Problem: I found that the OnPropertyChangedEvent of the INotifyPropertyChanged Interface needs to be called whenever a programmatic update on the
IsExpanded property is updated. As for the Original Problem, this does not seem to be the case, and I'm still trying to figure out what is going wrong. :)
I'm led to believe that you cannot replace the collection, only alter its contents.
a Boolean condition in the property that I'm binding too
I am not completely sure what you mean here, but I take the guess that this is the IsSelected property on your "commands" and that you forgot to raise PropertyChanged for that property.

Resources