Strange behaviour in the WPF DataTemplate and property. - wpf

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.

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?

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.

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.

WPF: Dynamically change ListBox's ItemTemplate based on ListBox Items Size

I need to change the DataTemplate of my ListBox, based on the ListBox items count. I have come up with the following XAML:
<Window.Resources>
<DataTemplate x:Key="DefaultTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Text}"/>
<TextBlock Text="default template" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="OtherTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Text}"/>
<TextBlock Text="other template" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<ListBox Name="listBox1" ItemsSource="{Binding Path=Items}">
<ListBox.Style>
<Style TargetType="{x:Type ListBox}">
<Setter Property="ItemTemplate" Value="{StaticResource DefaultTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Items.Count}" Value="1">
<Setter Property="ItemTemplate" Value="{StaticResource OtherTemplate}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Style>
</ListBox>
With the above XAML, once I added two or more items to the bound list, the data template changed as expected (from other to default). However, if I remove the first item in the list with more than two items, the entire listbox just becomes empty (I verified that the bound list is non-empty). Removing the second item in a two items list works fine though (i.e. template changed from default to other).
Any ideas why this is happening? Or perhaps I went about the wrong way to solve this problem?
you could use data triggers, or you could use a DataTemplateSelector Here is an article that shows the basics. and here is the MSDN on applying it to the items control (also, a listbox)
I can't speak for the exact problem or the cause, but it is because a DataTrigger is setting a template when the count is 1 and only 1.
You can do 1 of 3 things to solve this problem, but only 1 I would recommend.
a) Implement your own DataTrigger by deriving from System.Windows.TriggerBase
b) Use an implementation of System.Windows.Data.IValueConverter that will convert from ItemsControl.Items.Count into a DataTemplate. Retrieve the templates by placing an element in scope of your resources as Binding.ConverterParameter, casting them to FrameWorkElement and call FrameWorkElement.FindResource().
C) This is my recommendation, write your own DataTemplateSelector to do the grunt work. This mechanism is specifically targeted at the functionality you with you achieve. I recently wrote one that will pick a DataTemplate based on the type of the source object without requiring a DataTemplate with no x:Key set. Using Properties on the template selector, you can pass DataTemplates into the DataTemplateSelector using XAML, removing that FindResource code 'todo' list.

Resources