Привязка панели команд UWP

можно ли связать UWP CommandBar с чем-то вроде ObservableCollection или около того?

Чего я хочу добиться, так это привязать мои CommandBar из моих NavigationView к объекту определенного Page, чтобы AppBarButton динамически менялись в зависимости от текущего Page

Что я пробовал:

MainPage.xaml

    <NavigationView.HeaderTemplate>
        <DataTemplate>
            <Grid>
                <CommandBar Grid.Column="1"
                        HorizontalAlignment="Right"
                        VerticalAlignment="Top"
                        DefaultLabelPosition="Right"
                        Background="{ThemeResource SystemControlBackgroundAltHighBrush}"  Content="{Binding Path=Content.AppBarButtonList, ElementName=rootFrame}">
                </CommandBar>
            </Grid>
        </DataTemplate>
    </NavigationView.HeaderTemplate>

SomePage.xaml.cs

    public ObservableCollection<AppBarButton> AppBarButtonList = new ObservableCollection<AppBarButton> {
        new AppBarButton { Icon = new SymbolIcon(Symbol.Accept), Label="Bla" },
        new AppBarButton{Icon=new SymbolIcon(Symbol.Add),Label="Add"}
    };

Но CommandBar ничего не показывает.

Спасибо.


person cybertronic    schedule 22.03.2018    source источник


Ответы (2)


Мое исходное решение заключалось в использовании свойства PrimaryCommands для привязки команд, но оказалось, что это свойство доступно только для чтения.

Моим решением проблемы будет использование поведения.

Сначала добавьте ссылку на Microsoft.Xaml.Behaviors.Uwp.Managed из NuGet.

Затем добавьте в свой проект следующее поведение:

public class BindableCommandBarBehavior : Behavior<CommandBar>
{
    public ObservableCollection<AppBarButton> PrimaryCommands
    {
        get { return (ObservableCollection<AppBarButton>)GetValue(PrimaryCommandsProperty); }
        set { SetValue(PrimaryCommandsProperty, value); }
    }

    public static readonly DependencyProperty PrimaryCommandsProperty = DependencyProperty.Register(
        "PrimaryCommands", typeof(ObservableCollection<AppBarButton>), typeof(BindableCommandBarBehavior), new PropertyMetadata(default(ObservableCollection<AppBarButton>), UpdateCommands));

    private static void UpdateCommands(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
    {
        if (!(dependencyObject is BindableCommandBarBehavior behavior)) return;
        var oldList = dependencyPropertyChangedEventArgs.OldValue as ObservableCollection<AppBarButton>;
        if (dependencyPropertyChangedEventArgs.OldValue != null)
        {
            oldList.CollectionChanged -= behavior.PrimaryCommandsCollectionChanged;
        }

        var newList = dependencyPropertyChangedEventArgs.NewValue as ObservableCollection<AppBarButton>;
        if (dependencyPropertyChangedEventArgs.NewValue != null)
        {
            newList.CollectionChanged += behavior.PrimaryCommandsCollectionChanged;
        }
        behavior.UpdatePrimaryCommands();
    }


    private void PrimaryCommandsCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        UpdatePrimaryCommands();
    }


    private void UpdatePrimaryCommands()
    {
        if (PrimaryCommands != null)
        {
            AssociatedObject.PrimaryCommands.Clear();
            foreach (var command in PrimaryCommands)
            {
                AssociatedObject.PrimaryCommands.Add(command);
            }
        }
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        if (PrimaryCommands != null)
        {
            PrimaryCommands.CollectionChanged -= PrimaryCommandsCollectionChanged;
        }
    }
}

Такое поведение по существу создает фальшивое свойство PrimaryCommands, которое можно привязать, а также отслеживает события изменения коллекции. Всякий раз, когда происходит изменение, команды перестраиваются.

Наконец, проблема в вашем коде заключается в том, что ваш AppBarButtonList — это просто поле, а не свойство. Измените это следующим образом:

public ObservableCollection<AppBarButton> AppBarButtonList { get; } = new ObservableCollection<AppBarButton> {
    new AppBarButton { Icon = new SymbolIcon(Symbol.Accept), Label="Bla" },
    new AppBarButton{Icon=new SymbolIcon(Symbol.Add),Label="Add"}
};

Обратите внимание на {get ;}, который был добавлен перед оператором присваивания.

Теперь вы можете использовать поведение в XAML следующим образом:

<CommandBar>
    <interactivity:Interaction.Behaviors>
        <local:BindableCommandBarBehavior PrimaryCommands="{Binding Path=Content.AppBarButtonList, ElementName=rootFrame}" />
    </interactivity:Interaction.Behaviors>
</CommandBar>

Это ни в коем случае не идеальное решение, и его можно улучшить, чтобы разрешить привязку различных типов коллекций и многое другое, но оно должно охватывать ваш сценарий. Альтернативным решением было бы реализовать пользовательскую версию панели команд с новым дополнительным свойством зависимости непосредственно для типа, но я использовал поведение, чтобы прояснить для пользователя, что это «добавленная» функциональность, а не встроенная. .

person Martin Zikmund    schedule 22.03.2018
comment
Хорошо, не могли бы вы привести пример, как связать PirmaryCommands с CommandBar? - person cybertronic; 22.03.2018
comment
Я удивлен, но кажется, что свойство не может быть привязано к. Но я думаю, что я на пути к решению. Я дам вам знать в обновлении в ближайшее время. - person Martin Zikmund; 22.03.2018
comment
Большое спасибо за ваше решение, но мне интересно, как это сложно сделать. Или мой подход к интеграции CommandBar в NavigationView необычен? Должен ли я просто реализовать CommandBarдля каждого Page, который у меня есть? - person cybertronic; 22.03.2018
comment
Причина, вероятно, в том, что рекомендуемый вариант использования — создание предопределенного набора статических команд, которые объявлены в XAML, а не в коде. Обычно CommandBar находится в контексте контекста, поэтому он обычно находится рядом с контентом на том же уровне. В вашем случае вы помещаете его в заголовок контейнера, чтобы он был извлечен из самого содержимого. Однако в данном случае это логично, потому что вы хотите, чтобы команды находились в заголовке навигационного представления, чтобы они были контекстными для открытой страницы. - person Martin Zikmund; 22.03.2018

Я нашел этот ответ очень полезным. Я сделал еще несколько настроек, например использовал DataTemplateSelector для удаления ссылок пользовательского интерфейса, таких как «AppBarButton», из источника данных с возможностью привязки.

public class BindableCommandBarBehavior : Behavior<CommandBar>
{
    public static readonly DependencyProperty PrimaryCommandsProperty = DependencyProperty.Register(
        "PrimaryCommands", typeof(object), typeof(BindableCommandBarBehavior),
        new PropertyMetadata(null, UpdateCommands));

    public static readonly DependencyProperty ItemTemplateSelectorProperty = DependencyProperty.Register(
        "ItemTemplateSelector", typeof(DataTemplateSelector), typeof(BindableCommandBarBehavior),
        new PropertyMetadata(null, null));

    public DataTemplateSelector ItemTemplateSelector
    {
        get { return (DataTemplateSelector)GetValue(ItemTemplateSelectorProperty); }
        set { SetValue(ItemTemplateSelectorProperty, value); }
    }

    public object PrimaryCommands
    {
        get { return  GetValue(PrimaryCommandsProperty); }
        set { SetValue(PrimaryCommandsProperty, value); }
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        if (PrimaryCommands is INotifyCollectionChanged notifyCollectionChanged)
        {
            notifyCollectionChanged.CollectionChanged -= PrimaryCommandsCollectionChanged;
        }
    }

    private void UpdatePrimaryCommands()
    {
        if (AssociatedObject == null)
            return;

        if (PrimaryCommands == null)
            return;

        AssociatedObject.PrimaryCommands.Clear();

        if (!(PrimaryCommands is IEnumerable enumerable))
        {
            AssociatedObject.PrimaryCommands.Clear();
            return;
        }


        foreach (var command in enumerable)
        {
            var template = ItemTemplateSelector.SelectTemplate(command, AssociatedObject);

            if (!(template?.LoadContent() is FrameworkElement dependencyObject))
                continue;

            dependencyObject.DataContext = command;

            if (dependencyObject is ICommandBarElement icommandBarElement)
                AssociatedObject.PrimaryCommands.Add(icommandBarElement);
        }
    }

    protected override void OnAttached()
    {
        base.OnAttached();
        UpdatePrimaryCommands();
    }

    private void PrimaryCommandsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        UpdatePrimaryCommands();
    }

    private static void UpdateCommands(DependencyObject dependencyObject,
        DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
    {
        if (!(dependencyObject is BindableCommandBarBehavior behavior)) return;
        if (dependencyPropertyChangedEventArgs.OldValue is INotifyCollectionChanged oldList)
        {
            oldList.CollectionChanged -= behavior.PrimaryCommandsCollectionChanged;
        }

        if (dependencyPropertyChangedEventArgs.NewValue is INotifyCollectionChanged newList)
        {
            newList.CollectionChanged += behavior.PrimaryCommandsCollectionChanged;
        }

        behavior.UpdatePrimaryCommands();
    }
}

DataTemplateSelector:

public class CommandBarMenuItemTemplateSelector : DataTemplateSelector
{
    public DataTemplate CbMenuItemTemplate { get; set; }


    protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
    {
        if (item is ContextAction)
        {
            return CbMenuItemTemplate;
        }

        return base.SelectTemplateCore(item, container);
    }
}

Xaml для шаблонов:

 <DataTemplate x:Key="CbMenuItemTemplate">
    <AppBarButton
        Command="{Binding Command}"
        Icon="Add"
        Label="{Binding Text}" />
</DataTemplate>

<viewLogic:CommandBarMenuItemTemplateSelector x:Key="CommandBarMenuItemTemplateSelector" 
                                              CbMenuItemTemplate="{StaticResource CbMenuItemTemplate}" />

Применение:

  <CommandBar>
    <interactivity:Interaction.Behaviors>
       <viewLogic:BindableCommandBarBehavior ItemTemplateSelector="{StaticResource CommandBarMenuItemTemplateSelector}" PrimaryCommands="{Binding ContextActions}" />
    </interactivity:Interaction.Behaviors>
  </CommandBar>

Где ContextActions — это ObservableCollection моего класса ContextAction.

person naishx    schedule 22.06.2018