Jonathan Wood Jonathan Wood - 3 months ago 46
C# Question

Styling RibbonComboBox contents

I'm learning WPF and the

RibbonComboBox
control has given me weeks of headaches.

I finally seem to be getting some basic functionality working. Now I'm stuck with what should be trivial but, like the rest of WPF, is not.

Here is a section of my XAML:

<RibbonGroup Header="Category">
<RibbonComboBox Label="Category:" HorizontalContentAlignment="Left" SelectionBoxWidth="250">
<RibbonGallery ColumnsStretchToFill="True" SelectedItem="{Binding SelectedCategory}">
<RibbonGalleryCategory DisplayMemberPath="Text" MaxColumnCount="1" ItemsSource="{Binding Categories}">
</RibbonGalleryCategory>
</RibbonGallery>
</RibbonComboBox>
<RibbonComboBox Label="Subcategory:" HorizontalContentAlignment="Left" SelectionBoxWidth="250">
<RibbonGallery MaxColumnCount="1" ColumnsStretchToFill="True" SelectedItem="{Binding SelectedSubcategory}">
<RibbonGalleryCategory DisplayMemberPath="Text" ItemsSource="{Binding Subcategories}">
</RibbonGalleryCategory>
</RibbonGallery>
</RibbonComboBox>
<RibbonButton Label="Edit Categories" Command="local:EditCommands.Categories" SmallImageSource="Images\categories_sm.png" ToolTipTitle="Edit Categories" ToolTipDescription="Add, edit or delete categories and subcategories" ToolTipImageSource="Images\categories_sm.png"></RibbonButton>
</RibbonGroup>


The problem I was having is that the selection portion of the combo box drop down was only as wide as the text. (I want it to be as wide as the full drop down.)

As you can see, I added some
MaxColumnCount
and
ColumnStretchToFill
attributes. These initially seem to work, but...


  1. When the code refreshes the content, the
    ColumnStretchToFill
    setting appears to be discarded and the selection bar is again only as wide as the selection text.

  2. There is a hierarchy of
    RibbonComboBox
    ,
    RibbonGallery
    and
    RibbonGalleryCategory
    . I could not find out why. More than one of those elements have the
    MaxColumnCount
    and
    ColumnStretchToFill
    attributes (as well as other comment attributes). How do I know which element I should set these attributes for?


Answer

To achieve your goal, do the following:

<Ribbon>
    <RibbonGroup Header="Category" Height="100">
        <RibbonComboBox Label="Category:" >
            <RibbonGallery SelectedItem="{Binding SelectedCategory, Mode=TwoWay, IsAsync=True}" >
                <RibbonGalleryCategory  ItemsSource="{Binding Categories}" DisplayMemberPath="Name" ColumnsStretchToFill="True" MaxColumnCount="1" IsSharedColumnSizeScope="True" />
            </RibbonGallery>
        </RibbonComboBox>
        <RibbonComboBox Label="Subcategory:" >
            <RibbonGallery SelectedItem="{Binding SelectedSubCategory}" >
                <RibbonGalleryCategory  ItemsSource="{Binding SelectedCategory.SubCategories}" DisplayMemberPath="Name" ColumnsStretchToFill="True" MaxColumnCount="1" IsSharedColumnSizeScope="True"/>
            </RibbonGallery>
        </RibbonComboBox>
        <RibbonButton Label="Edit Categories"  ToolTipTitle="Edit Categories" ToolTipDescription="Add, edit or delete categories and subcategories" Command="{Binding AddCatCommand}"></RibbonButton>
    </RibbonGroup>
</Ribbon>

You have to set IsSharedColumnSizeScope="True" in order to make the items stretch to the ComboBox. That insures a proper layout.

The above example shows you the difference, since i didnt set that property on your second ComboBox.

Hope this helps.

Edit

Here a simple Model

public class Category {
        private ObservableCollection<Category> _subCats = new ObservableCollection<Category>();
        public string Name { get; set; }

        public ObservableCollection<Category> SubCategories => this._subCats;
    }

Now some ViewModel-stuff i put in CodeBehind

public partial class Window1 : INotifyPropertyChanged {

        private Category _selectedCategory;
        private ObservableCollection<Category> _categories = new ObservableCollection<Category>();
        private Category _selectedSubCategory;

        public Window1() {
            InitializeComponent();
            var cat = new Category() { Name = "Category 1" };
            cat.SubCategories.Add(new Category { Name = "Cat 1 - Subcat 1" });
            cat.SubCategories.Add(new Category { Name = "Cat 1 - Subcat 2" });
            cat.SubCategories.Add(new Category { Name = "Cat 1 - Subcat 3" });
            var cat2 = new Category() { Name = "Category 2" };
            cat2.SubCategories.Add(new Category { Name = "Cat 2 - Subcat 1" });
            cat2.SubCategories.Add(new Category { Name = "Cat 2 - Subcat 2" });
            var cat3 = new Category() { Name = "Category 3" };
            cat2.SubCategories.Add(new Category { Name = "Cat 3 - Subcat 2" });
            this.Categories.Add(cat);
            this.Categories.Add(cat2);
            this.Categories.Add(cat3);
            this.SelectedCategory = this.Categories.First();
            this.DataContext = this;
        }

        public ICommand AddCatCommand => new RelayCommand(x => {
            var newCat = new Category { Name = "Im New" };
            newCat.SubCategories.Add(new Category { Name = "I'm new too" });
            this.Categories.Add(newCat);
        });

        public Category SelectedCategory {
            get { return _selectedCategory; }
            set {
                if (Equals(value, _selectedCategory)) return;
                _selectedCategory = value;
                OnPropertyChanged();
            }
        }

        public Category SelectedSubCategory {
            get { return _selectedSubCategory; }
            set {
                if (Equals(value, _selectedSubCategory)) return;
                _selectedSubCategory = value;
                OnPropertyChanged();
            }
        }

        public ObservableCollection<Category> Categories => this._categories;

        public event PropertyChangedEventHandler PropertyChanged;

        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

Clearance

As you might note, none of the used collections has a setter, preventing them from being overridden. If ItemsSource is used, the bound Collection should not reset, just cleared .Clear() and new items added as the Button in the above example will show you

Comments