如何使用 MultiBinding 在上下文菜单中隐藏分隔符?

问题描述 投票:0回答:3

我正在 wpf 树视图上使用上下文菜单,并且我几乎可以满足我的需求。在解释问题之前,让我先解释一下上下文菜单的 XAML 定义的作用。

对于上下文菜单中的每个菜单项,我们都有一个命令,可以根据命令 CanExecute 方法禁用或启用菜单项。每个命令都会根据 CanExecute 的结果设置相应菜单项的 IsEnabled 属性。

每个菜单项的 IsEnabled 都绑定到 BooleanToVisibilityConverter,它将 IsEnabled bool 值转换为 Collapse 或 Visible 值,以绑定菜单项的 Visibility 属性。这再次工作得很好,我的菜单项显示和隐藏得很好。

现在来说说问题。在下面的 XAML 中,分隔符上方有两个菜单项(addCategoryMenuItem 和 removeCategoryMenuItem)。我正在尝试通过 IMultiValueConverter (MultiBooleanToVisibilityConverter) 的自定义实现对这两个菜单项的 IsEnabled 属性进行多重绑定,以便在禁用这两个菜单项时,我可以将分隔符的可见性属性设置为折叠,从而在菜单项被禁用。

对于 Converter(MultiBooleanToVisibilityConverter) 中的 Convert 方法,参数值(对象 [] 值)我在数组中得到两个包含值“{DependencyProperty.UnsetValue}”的项目。这些不能转换为布尔值,因此无法计算出我的可见性值。

也许与 MultiBinding 中使用的 ElementName 有关。找不到元素吗?我尝试过使用RelativeSource,即查找祖先等。但我只是感到困惑。我花了几个小时在这上面,所以现在我把它留给社区。

亲切的问候

穆罕默德

<ContextMenu x:Key="CategoryMenu"> <ContextMenu.ItemContainerStyle> <Style TargetType="{x:Type Control}"> <Setter Property="Visibility" Value="{Binding Path=IsEnabled, RelativeSource={RelativeSource Self}, Mode=OneWay, Converter={StaticResource booleanToVisibilityConverter}}" /> </Style> </ContextMenu.ItemContainerStyle> <ContextMenu.Items> <MenuItem x:Name="addCategoryMenuItem" Header="add category" Command="{Binding AddCategory}"> <MenuItem.Icon> <Image Source="/Images/add.png" Width="16" Height="16" /> </MenuItem.Icon> </MenuItem> <MenuItem x:Name="removeCategoryMenuItem" Header="remove category" Command="{Binding RemoveCategory}"> <MenuItem.Icon> <Image Source="/Images/remove.png" Width="16" Height="16" /> </MenuItem.Icon> </MenuItem> <Separator> <Separator.Visibility> <MultiBinding Converter="{StaticResource multiBooleanToVisibilityConverter}"> <Binding Mode="OneWay" ElementName="addCategoryMenuItem" Path="IsEnabled" /> <Binding Mode="OneWay" ElementName="removeCategoryMenuItem" Path="IsEnabled" /> </MultiBinding> </Separator.Visibility> </Separator> <MenuItem x:Name="refreshCategoryMenuItem" Header="refresh" Command="{Binding RefreshCategory}"> <MenuItem.Icon> <Image Source="/Images/refresh.png" Width="16" Height="16" /> </MenuItem.Icon> </MenuItem> </ContextMenu.Items> </ContextMenu>
    
wpf contextmenu hide separator multibinding
3个回答
4
投票
我扩展了普通分隔符来创建一个分隔符,该分隔符可以根据父 ItemsControl 中的其他项目自动确定是否应显示它。

public class AutoVisibilitySeparator : Separator { public AutoVisibilitySeparator() { if (DesignerProperties.GetIsInDesignMode(this)) return; Visibility = Visibility.Collapsed; // Starting collapsed so we don't see them disappearing Loaded += OnLoaded; } private void OnLoaded(object sender, RoutedEventArgs e) { // We have to wait for all siblings to update their visibility before we update ours. // This is the best way I've found yet. I tried waiting for the context menu opening or visibility changed, on render and lots of other events Dispatcher.BeginInvoke(new Action(UpdateVisibility), DispatcherPriority.Render); } private void UpdateVisibility() { var showSeparator = false; // Go through each sibling of the parent context menu looking for a visible item before and after this separator var foundThis = false; var foundItemBeforeThis = false; foreach (var visibleItem in ((ItemsControl)Parent).Items.OfType<UIElement>().Where(i => i.Visibility == Visibility.Visible || i == this)) { if (visibleItem == this) { // If there were no visible items prior to this separator then we hide it if (!foundItemBeforeThis) break; foundThis = true; } else if (visibleItem is AutoVisibilitySeparator || visibleItem is Separator) { // If we already found this separator and this next item is not a visible item we hide this separator if (foundThis) break; foundItemBeforeThis = false; // The current item is a separator so we reset the search for an item } else { if (foundThis) { // We found a visible item after finding this separator so we're done and should show this showSeparator = true; break; } foundItemBeforeThis = true; } } Visibility = showSeparator ? Visibility.Visible : Visibility.Collapsed; } }
    

2
投票
好的,经过一番休息,我已经解决了这个问题。我必须使用RelativeSource 和FindAncestor 来获取上下文菜单对象,然后访问项目集合,然后使用索引器值来获取菜单项。我认为如果我可以使用菜单项名称会更好,因为我不喜欢代码或 xaml 中的幻数。

<Separator> <Separator.Visibility> <MultiBinding Converter="{StaticResource multiBooleanToVisibilityConverter}"> <Binding Mode="OneWay" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}" Path="Items[0].IsEnabled" /> <Binding Mode="OneWay" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}" Path="Items[1].IsEnabled" /> </MultiBinding> </Separator.Visibility> </Separator>
    

0
投票
Michael Olsen 的想法非常好 - 我修改了它,以便分隔符检查分隔符之前和之后是否有有效的项目。

public class AutoSeparator : Separator { public AutoSeparator() { if (DesignerProperties.GetIsInDesignMode(this)) return; Visibility = Visibility.Collapsed; // Starting collapsed so we don't see them disappearing Loaded += OnLoaded; } private void OnLoaded(object sender, RoutedEventArgs e) { Dispatcher.BeginInvoke(new Action(UpdateVisibility), DispatcherPriority.Render); } private void UpdateVisibility() { ItemCollection items = ((ItemsControl)Parent).Items; int index = items.IndexOf(this); if (index == -1) return; int i; for (i = index - 1; i >= 0; i--) { if (items[i] is UIElement uiElement) { // Invisible item cannot be valid predecessor if (uiElement.Visibility != Visibility.Visible) continue; // Separator is invalid predecessor else if (uiElement is Separator or AutoSeparator) return; // Anything else is valid predecessor else break; } } // No valid item found before separator if (i < 0) return; for (i = index + 1; i < items.Count; i++) { if (items[i] is UIElement uiElement) { // Invisible item cannot be valid successor if (uiElement.Visibility != Visibility.Visible) continue; // Separator is invalid successor else if (uiElement is Separator or AutoSeparator) return; // Anything else is valid successor else break; } } if (i >= items.Count) return; Visibility = Visibility.Visible; } }
    
© www.soinside.com 2019 - 2024. All rights reserved.