How to access Quick Access tool bar command `Add to Quick Access Tool` if source binding applicable
Asked Answered
C

4

11

How can I add Quick Access Item container default by RibbonLibrary if I have binded collection for it. Its throws Operation is not valid while ItemSource is in use while is I add Quick Access tool item from UI.

<r:Ribbon Name="ribbon">

        <r:Ribbon.QuickAccessToolBar>

            <r:RibbonQuickAccessToolBar ItemsSource ="{Binding QuickMenuItems, Mode=OneWay}">
                <r:RibbonQuickAccessToolBar.ItemTemplate>
                    <DataTemplate>
                        <StackPanel>
                            <r:RibbonButton QuickAccessToolBarId="{Binding RibbonId}" Label="{Binding Label}" SmallImageSource="{Binding ImageUri}" Command="{Binding Command}"/>
                        </StackPanel>
                    </DataTemplate>
                </r:RibbonQuickAccessToolBar.ItemTemplate>
            </r:RibbonQuickAccessToolBar>

        </r:Ribbon.QuickAccessToolBar>

        <r:RibbonTab Header="Home">
            <r:RibbonGroup x:Name="Clipboard" ItemsSource ="{Binding MenuItems, Mode=OneWay}" >

                <r:RibbonGroup.ItemTemplate>
                    <DataTemplate>
                        <StackPanel>
                            <r:RibbonButton QuickAccessToolBarId="{Binding RibbonId}" Label="{Binding Label}" SmallImageSource="{Binding ImageUri}" Command="{Binding Command}"/>
                        </StackPanel>
                    </DataTemplate>
                </r:RibbonGroup.ItemTemplate>

            </r:RibbonGroup>
        </r:RibbonTab>

    </r:Ribbon>


 ObservableCollection<RibbonItem> _MenuItems;
 ObservableCollection<RibbonItem> _QuickMenuItems;

 public ObservableCollection<RibbonItem> MenuItems
 {
      get { return _MenuItems; }
 }
 public ObservableCollection<RibbonItem> QuickMenuItems
 {
      get { return _QuickMenuItems; }
 }
public class RibbonItem
{
    public RibbonItem(string label, string imageUri, ICommand command, string ribbonId)
    {
        Label = label;
        ImageUri = imageUri;
        Command = command;
    }

    public string Label { get; private set; }

    public string ImageUri { get; private set; }

    public ICommand Command { get; private set; }

    public string RibbonId { get; private set; }
}

Error while

enter image description here

enter image description here
Add comment if not clear.

Calva answered 22/7, 2015 at 13:12 Comment(0)
S
1

This will allow you to add quick menu items from both ribbon control and ViewModel. I have used a ListBox to act as a proxy between the ViewModel and the RibbonQuickAccessToolBar. When items are added to the ListBox, the view will add them to the RibbonQuickAccessToolBar.

Create a collapsed ListBox that will bind to the collection in the ViewModel and remove the ItemsSource binding for the RibbonQuickAccessToolBar:

<ListBox ItemsSource="{Binding QuickMenuItems, Mode=OneWay}"
            x:Name="ProxyListBox"
            Visibility="Collapsed"/>
<ribbon:Ribbon Name="ribbon">
    <ribbon:Ribbon.QuickAccessToolBar>
        <ribbon:RibbonQuickAccessToolBar x:Name="QuickAccessToolBar" DataContextChanged="QuickAccessToolBar_OnDataContextChanged">
            <ribbon:RibbonQuickAccessToolBar.ItemTemplate>
                <DataTemplate>
                    <StackPanel>
                        <ribbon:RibbonButton QuickAccessToolBarId="{Binding RibbonId}" Label="{Binding Label}" SmallImageSource="{Binding ImageUri}" Command="{Binding Command}"/>
                    </StackPanel>
                </DataTemplate>
            </ribbon:RibbonQuickAccessToolBar.ItemTemplate>
        </ribbon:RibbonQuickAccessToolBar>
    </ribbon:Ribbon.QuickAccessToolBar>
    <ribbon:RibbonTab Header="Home">
        <ribbon:RibbonGroup x:Name="Clipboard" ItemsSource ="{Binding MenuItems, Mode=OneWay}" >
            <ribbon:RibbonGroup.ItemTemplate>
                <DataTemplate>
                    <StackPanel>
                        <ribbon:RibbonButton QuickAccessToolBarId="{Binding RibbonId}" Label="{Binding Label}" SmallImageSource="{Binding ImageUri}" Command="{Binding Command}"/>
                    </StackPanel>
                </DataTemplate>
            </ribbon:RibbonGroup.ItemTemplate>
        </ribbon:RibbonGroup>
    </ribbon:RibbonTab>
</ribbon:Ribbon>

In the code-behind use the DataContextChanged of the ListBox to attach an event handler for the ListBox.ItemsSource's CollectionChanged event:

private void ProxyListBox_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    if (e.NewItems != null)
    {
        foreach (var newItem in e.NewItems)
        {
            QuickAccessToolBar.Items.Add(newItem);
        }
    }

    if (e.OldItems != null)
    {
        foreach (var oldItem in e.OldItems)
        {
            QuickAccessToolBar.Items.Remove(oldItem);
        }
    }
}

private void QuickAccessToolBar_OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
    ((INotifyCollectionChanged)ProxyListBox.Items).CollectionChanged += ProxyListBox_CollectionChanged;
}

The ViewModel is the same as before:

class RibbonViewModel
{
    ObservableCollection<RibbonItem> _MenuItems;

    ObservableCollection<RibbonItem> _QuickMenuItems;

    public ObservableCollection<RibbonItem> MenuItems
    {
        get { return _MenuItems; }
    }

    public ObservableCollection<RibbonItem> QuickMenuItems
    {
        get { return _QuickMenuItems; }
    }

    public RibbonViewModel()
    {
        _QuickMenuItems = new ObservableCollection<RibbonItem>();
        _MenuItems = new ObservableCollection<RibbonItem>();
    }

    public class RibbonItem
    {
        public RibbonItem(string label, string imageUri, ICommand command)
        {
            Label = label;
            ImageUri = imageUri;
            Command = command;
        }

        public string Label { get; private set; }

        public string ImageUri { get; private set; }

        public ICommand Command { get; private set; }

        public string RibbonId { get; private set; }
    }
}
Selectee answered 6/8, 2015 at 9:59 Comment(0)
S
4

The problem is that the ContextMenu is trying to add items to the collection using ItemCollection.Add(), but this method is not supported if an ItemsSource is being used to populate the collection instead and the method will throw an exception.

See the source code: ItemCollection.cs

One solution would be to hide the ContextMenu to avoid its unsupported functions being called, or try to replace it with your own custom ContextMenu, which binds to a command on the ViewModel and updates the QuickMenuItems.

There may be some kind of hack using attached properties.

Selectee answered 31/7, 2015 at 11:38 Comment(2)
So any other alternative or solutions?Calva
Still trying to figure it outSelectee
A
1

One workaround would be to not use RibbonQuickAccessToolBar.ItemsSource. If you need to be notified of changes to the QuickAccessToolbar, subscribe to INotifyCollectionChanged of RibbonQuickAccessToolBar.Items.

   public RibbonWindow() {
        InitializeComponent();
        ((INotifyCollectionChanged)QuickAccessToolBar.Items).CollectionChanged += QuickAccessToolBar_ItemsCollectionChanged;
    }

    private void QuickAccessToolBar_ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
        switch (e.Action) {
           //Do stuff or synchronize to observableCollection
        }
    }
Antioch answered 3/8, 2015 at 6:33 Comment(0)
S
1

This will allow you to add quick menu items from both ribbon control and ViewModel. I have used a ListBox to act as a proxy between the ViewModel and the RibbonQuickAccessToolBar. When items are added to the ListBox, the view will add them to the RibbonQuickAccessToolBar.

Create a collapsed ListBox that will bind to the collection in the ViewModel and remove the ItemsSource binding for the RibbonQuickAccessToolBar:

<ListBox ItemsSource="{Binding QuickMenuItems, Mode=OneWay}"
            x:Name="ProxyListBox"
            Visibility="Collapsed"/>
<ribbon:Ribbon Name="ribbon">
    <ribbon:Ribbon.QuickAccessToolBar>
        <ribbon:RibbonQuickAccessToolBar x:Name="QuickAccessToolBar" DataContextChanged="QuickAccessToolBar_OnDataContextChanged">
            <ribbon:RibbonQuickAccessToolBar.ItemTemplate>
                <DataTemplate>
                    <StackPanel>
                        <ribbon:RibbonButton QuickAccessToolBarId="{Binding RibbonId}" Label="{Binding Label}" SmallImageSource="{Binding ImageUri}" Command="{Binding Command}"/>
                    </StackPanel>
                </DataTemplate>
            </ribbon:RibbonQuickAccessToolBar.ItemTemplate>
        </ribbon:RibbonQuickAccessToolBar>
    </ribbon:Ribbon.QuickAccessToolBar>
    <ribbon:RibbonTab Header="Home">
        <ribbon:RibbonGroup x:Name="Clipboard" ItemsSource ="{Binding MenuItems, Mode=OneWay}" >
            <ribbon:RibbonGroup.ItemTemplate>
                <DataTemplate>
                    <StackPanel>
                        <ribbon:RibbonButton QuickAccessToolBarId="{Binding RibbonId}" Label="{Binding Label}" SmallImageSource="{Binding ImageUri}" Command="{Binding Command}"/>
                    </StackPanel>
                </DataTemplate>
            </ribbon:RibbonGroup.ItemTemplate>
        </ribbon:RibbonGroup>
    </ribbon:RibbonTab>
</ribbon:Ribbon>

In the code-behind use the DataContextChanged of the ListBox to attach an event handler for the ListBox.ItemsSource's CollectionChanged event:

private void ProxyListBox_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    if (e.NewItems != null)
    {
        foreach (var newItem in e.NewItems)
        {
            QuickAccessToolBar.Items.Add(newItem);
        }
    }

    if (e.OldItems != null)
    {
        foreach (var oldItem in e.OldItems)
        {
            QuickAccessToolBar.Items.Remove(oldItem);
        }
    }
}

private void QuickAccessToolBar_OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
    ((INotifyCollectionChanged)ProxyListBox.Items).CollectionChanged += ProxyListBox_CollectionChanged;
}

The ViewModel is the same as before:

class RibbonViewModel
{
    ObservableCollection<RibbonItem> _MenuItems;

    ObservableCollection<RibbonItem> _QuickMenuItems;

    public ObservableCollection<RibbonItem> MenuItems
    {
        get { return _MenuItems; }
    }

    public ObservableCollection<RibbonItem> QuickMenuItems
    {
        get { return _QuickMenuItems; }
    }

    public RibbonViewModel()
    {
        _QuickMenuItems = new ObservableCollection<RibbonItem>();
        _MenuItems = new ObservableCollection<RibbonItem>();
    }

    public class RibbonItem
    {
        public RibbonItem(string label, string imageUri, ICommand command)
        {
            Label = label;
            ImageUri = imageUri;
            Command = command;
        }

        public string Label { get; private set; }

        public string ImageUri { get; private set; }

        public ICommand Command { get; private set; }

        public string RibbonId { get; private set; }
    }
}
Selectee answered 6/8, 2015 at 9:59 Comment(0)
C
0

Finally I am able to solve my problem by code behind for that I am using Attach Behavior for RibbonWindow, here is solution:

public class RibbonWindowQuickAccessItemBehaviour : Behavior<RibbonWindow>
{
    protected override void OnAttached()
    {
        base.OnAttached();

        AssociatedObject.Loaded += RibbonControl_Loaded;
        AssociatedObject.Closing += RibbonControl_Closing;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();

        AssociatedObject.Loaded -= RibbonControl_Loaded;
        AssociatedObject.Closing -= RibbonControl_Closing;
    }

    private void RibbonControl_Closing(object sender, System.ComponentModel.CancelEventArgs e)
    {
        var ribbonWindow = (RibbonWindow)sender;

        var ribbon = ribbonWindow.FindAllChildrenOfType<Microsoft.Windows.Controls.Ribbon.Ribbon>().Where(a => a.Name == "ribbon").FirstOrDefault();

        if (ribbon == null) return;

        var dataContext = ribbon.DataContext;

        if (dataContext is IRibbonMainWindowViewModel)
        {
            IRibbon windowsRibbon = (dataContext as IRibbonMainWindowViewModel).WindowRibbon;

            windowsRibbon.QuickAccessMenuItems.Clear();

            if (ribbon.QuickAccessToolBar != null && ribbon.QuickAccessToolBar.Items != null && ribbon.QuickAccessToolBar.Items.Count > 0)
            {
                foreach (var item in ribbon.QuickAccessToolBar.Items)
                {
                    if (item is RibbonButton)
                    {
                        var ribbonItem = item as RibbonButton;

                        windowsRibbon.QuickAccessMenuItems.Add
                            (new Gno.Framework.ClientShell.Ribbon.MenuItem(ribbonItem.Label,
                                ribbonItem.SmallImageSource == null ? string.Empty : ribbonItem.SmallImageSource.ToString(),
                                ribbonItem.Command));
                    }
                }
            }
        }
    }

    private void RibbonControl_Loaded(object sender, RoutedEventArgs e)
    {
        var ribbonWindow = (RibbonWindow)sender;

        var ribbon = ribbonWindow.FindAllChildrenOfType<Microsoft.Windows.Controls.Ribbon.Ribbon>().Where(a => a.Name == "ribbon").FirstOrDefault();

        if (ribbon == null) return;

        var dataContext = ribbon.DataContext;

        if (dataContext is IRibbonMainWindowViewModel)
        {
            IRibbon windowsRibbon = (dataContext as IRibbonMainWindowViewModel).WindowRibbon;

            var quickAccessMenus = windowsRibbon.QuickAccessMenuItems;

            if (quickAccessMenus != null && quickAccessMenus.Count > 0)
            {
                ribbon.QuickAccessToolBar = new RibbonQuickAccessToolBar();

                foreach (var quickMenu in windowsRibbon.QuickAccessMenuItems)
                {
                    var ribbonButton = new RibbonButton();

                    ribbonButton.Command = quickMenu.Command;
                    ribbonButton.Label = quickMenu.Header;

                    if (!string.IsNullOrEmpty(quickMenu.ImageUri))
                        ribbonButton.SmallImageSource = new BitmapImage(new Uri(quickMenu.ImageUri));

                    ribbon.QuickAccessToolBar.Items.Add(ribbonButton);
                }
            }
        }
    }
}

Actually this is code to save QuickAccessToolBarItem, hence Window Close and Load event need to bind.

Calva answered 7/8, 2015 at 10:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.