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?
Related
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.
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.
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.
I was hoping to collapse certain ListBoxItems based on a property of their data context.
I came up with the following (trimmed for brevity)
<ListBox ItemsSource="{Binding SourceColumns}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsDeleted}" Value="True">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock VerticalAlignment="Center" Margin="5,0" Text="{Binding ColumnName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This "works" in that it does collapse the listboxitems that are marked as "IsDeleted", however the vertical scrollbar does not adjust for the "missing" items. As I'm scrolling, all of a sudden the bar gets bigger and bigger (without moving) until I scroll past the point of the hidden items, and then finally starts to move.
I also tried explicitly setting the height and width to 0 as well in the data trigger, to no avail.
Does anyone know if there's a workaround for this?
Enter CollectinViewSource
One thing you can do is connect your ListBox to your items through a CollectionViewSource.
What you do is create the collectionViewSource in XAML:
<Window.Resources>
<CollectionViewSource x:Key="cvsItems"/>
</Window.Resources>
Connect to it in your CodeBehind or ViewModel
Dim cvsItems as CollectionViewSource
cvsItems = MyWindow.FindResource("cvsItems")
and set it's source property to your collection of items.
cvsItems.Source = MyItemCollection
Then you can do filtering on it. The collectionViewSource maintains all of the items in the collection, but alters the View of those items based on what you tell it.
Filtering
To filter, create a CollectionView using your CollectionViewSource:
Dim MyCollectionView as CollectionView = cvsItems.View
Next write a filtering function:
Private Function FilterDeleted(ByVal item As Object) As Boolean
Dim MyObj = CType(item, MyObjectType)
If MyObj.Deleted = True Then Return False Else Return True End If
End Function
Finally, write something that makes the magic happen:
MyCollectionView .Filter = New Predicate(Of Object)(AddressOf FilterDeleted)
I usually have checkboxes or Radiobuttons in a hideable expander that lets me change my filtering options back and forth. Those are bound to properties each of which runs the filter function which evaluates all the filters and then returns whether the item should appear or not.
Let me know if this works for you.
Edit:
I almost forgot:
<ListBox ItemsSource="{Binding Source={StaticResource cvsItems}}"/>
The answer is to set the VirtualizingStackPanel.IsVirtual="False" in your listbox.
Why don't my listboxitems collapse?
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.