3

私は wpf ツリービューでコンテキスト メニューを使用しています。問題を説明する前に、コンテキスト メニューの XAML 定義が何をしているのかを説明しましょう。

コンテキスト メニューの各メニュー項目には、コマンドの CanExecute メソッドに基づいてメニュー項目を無効または有効にするコマンドがあります。各コマンドは、CanExecute の結果に応じて、対応するメニュー項目の IsEnabled プロパティを設定します。

各メニュー項目の IsEnabled は BooleanToVisibilityConverter にバインドされ、IsEnabled bool 値を Collapse または Visible 値に変換して、メニュー項目の Visibility プロパティにバインドされます。これも正常に機能し、メニュー項目は正常に表示および非表示になっています。

さて、問題です。以下の XAML では、セパレータの上に 2 つのメニュー項目 (addCategoryMenuItem と removeCategoryMenuItem) があります。IMul​​tiValueConverter (MultiBooleanToVisibilityConverter) のカスタム実装を介して、これら 2 つのメニュー項目の IsEnabled プロパティに MultiBinding しようとしています。これにより、2 つのメニュー項目が無効になっているときに Separator の Visibility プロパティを折りたたみに設定できるため、メニュー項目は無効です。

私の Converter(MultiBooleanToVisibilityConverter) の Convert メソッドでは、パラメーター値 (オブジェクト [] 値) の値「{DependencyProperty.UnsetValue}」を保持する配列内の 2 つの項目を取得します。これらはブール値にキャストできないため、Visibility 値を計算できません。

おそらく、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>
4

2 に答える 2

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>
于 2011-02-19T09:22:40.430 に答える
2

通常のセパレーターを拡張して、親 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;
    }
}
于 2016-12-15T18:18:13.050 に答える