ETG87 ETG87 - 2 months ago 9
C# Question

How to render a Ribbon from data objects using ItemsSource and DataTemplates

Im trying to supply a data template to my ribbon.

The ribbon is declared as following, and has an ItemTemplate attached to it.

<r:Ribbon Name="RibbonMain"
ItemTemplate="{StaticResource HomeRibbonTabTemplate}">
</r:Ribbon>


The Datatemplate is the following:

<Window.Resources>
<DataTemplate DataType="{x:Type local:RibbonContainer}"
x:Key="HomeRibbonTabTemplate">
<r:RibbonTab Header="{Binding Path=HeaderName}">
<r:RibbonGroup Header="{Binding Path=GroupName}">
</r:RibbonGroup>
</r:RibbonTab>
</DataTemplate>
</Window.Resources>


I do then attach the ItemsSource:

public MainWindow()
{
InitializeComponent();

var RibbonTabData = new ObservableCollection<RibbonContainer>();
RibbonTabData.Add(new RibbonContainer("HeaderName", "GroupName"));
RibbonMain.ItemsSource = RibbonTabData;
}


Lastly the class: (Which just contains two string fields)

class RibbonContainer
{
public string HeaderName
{
get;
set;
}

public string GroupName
{
get;
set;
}

public RibbonContainer(string _headername, string _groupname)
{
HeaderName = _headername;
GroupName = _groupname;
}
}


I get the unimpressive result of showing the fully qualified class name in the tab header and neither is the ribbongroup showing. (This is what the datatemplate should solve?)
What to do?

Best regards

Answer

I'm not exactly sure where to start, but perhaps with a short warning that, in trying to create a RibbonControl totally from data Binding and data items, you really are opening up a huge can of whoop ass on yourself. This is because the developers that designed the code for it used unconventional patterns for some of it and failed to adequately document how to do things with it. Some of the best sources will be found by searching on this website.

So anyway, if you're up for a painful, uphill struggle, read on. Your first mistake was trying to use a DataTemplate for the RibbonTab because it is extends System.Windows.Controls.ItemsControl and therefore requires a HierarchicalDataTemplate. Your second mistake was declaring the RibbonTab inside the template, as @devhedgehog mentioned in a comment.

You third mistake was setting the x:Key value for your DataTemplate and applying it to the Ribbon.ItemsTemplate property... I know, I know... a sensible enough thing to do if this wasn't a RibbonControl. You'll have to ask those developers as to why that doesn't work, but you're better off just accepting that it doesn't and adapting your code. You just need to remove the x:Key value and the Ribbon.ItemsTemplate property and let the Framework apply the template implicitly.

Now if you ever want more than one RibbonGroup, then your fourth mistake was defining that in the template for the RibbonTab. If you're going to do this properly, then your data classes will need to match the various levels of UI elements in the Ribbon. By this, I mean that you need to create a RibbonGroupData class too. That class needs a collection of RibbonButtonData objects that supply the data to each RibbonButton in the UI. So you should end up with something like this:

public class RibbonTabData : BaseDataType
{
    private string name = string.Empty;
    private ObservableCollection<RibbonGroupData> ribbonGroupData = new ObservableCollection<RibbonGroupData>();

    public string Name
    {
        get { return name; }
        set { name = value; NotifyPropertyChanged("Name"); }
    }

    public ObservableCollection<RibbonGroupData> RibbonGroupData
    {
        get { return ribbonGroupData; }
        set { ribbonGroupData = value; NotifyPropertyChanged("RibbonGroupData"); }
    }
}

public class RibbonGroupData : BaseDataType
{
    private string name = string.Empty;
    private ObservableCollection<RibbonButtonData> ribbonButtonData = new ObservableCollection<RibbonButtonData>();

    public string Name
    {
        get { return name; }
        set { name = value; NotifyPropertyChanged("Name"); }
    }

    public ObservableCollection<RibbonButtonData> RibbonButtonData
    {
        get { return ribbonButtonData; }
        set { ribbonButtonData = value; NotifyPropertyChanged("RibbonButtonData"); }
    }
}

public class RibbonButtonData : BaseDataType
{
    private string name = string.Empty;

    public string Name
    {
        get { return name; }
        set { name = value; NotifyPropertyChanged("Name"); }
    }
}

The BaseDataType class just implements the INotifyPropertyChanged interface. Of course, you'd need to add extra properties for ICommands and image sources, etc. You might even need different RibbonButtonData classes with different properties for different types of RibbonButtons and then you'd need a common RibbonButtonBaseData class that they all extended, so your collection could contain all the different types together. So there's lots more for you to do, but given this example code, you could display it in the Ribbon like this:

<Ribbon:RibbonWindow.Resources>
    <HierarchicalDataTemplate DataType="{x:Type DataTypes:RibbonTabData}" 
        ItemsSource="{Binding RibbonGroupData}">
        <TextBlock Text="{Binding Name}" />
    </HierarchicalDataTemplate>
    <DataTemplate DataType="{x:Type DataTypes:RibbonButtonData}">
        <Ribbon:RibbonButton Label="{Binding Name}" 
            LargeImageSource="/WpfRibbonApplication1;component/Images/LargeIcon.png" />
    </DataTemplate>
    <HierarchicalDataTemplate DataType="{x:Type DataTypes:RibbonGroupData}" 
        ItemsSource="{Binding RibbonButtonData}">
        <Ribbon:RibbonGroup Header="{Binding Name}" />
    </HierarchicalDataTemplate>
</Ribbon:RibbonWindow.Resources>
<Ribbon:Ribbon x:Name="Ribbon" ItemsSource="{Binding RibbonTabData}" />

Now in the view model that is set as the DataContext for the Window, I can add some dummy data to test that it all works:

RibbonTabData.Add(new RibbonTabData() { Name = "Tab 1", RibbonGroupData = new ObservableCollection<RibbonGroupData>() { new RibbonGroupData() { Name = "Group 1", RibbonButtonData = new ObservableCollection<RibbonButtonData>() { new RibbonButtonData() { Name = "Button 1" }, new RibbonButtonData() { Name = "Button 2" }, new RibbonButtonData() { Name = "Button 3" } } }, new RibbonGroupData() { Name = "Group 2", RibbonButtonData = new ObservableCollection<RibbonButtonData>() { new RibbonButtonData() { Name = "Button 1" }, new RibbonButtonData() { Name = "Button 2" } } } } });
RibbonTabData.Add(new RibbonTabData() { Name = "Tab 2" });
RibbonTabData.Add(new RibbonTabData() { Name = "Tab 3" });

And we get this:

enter image description here

However, even with this helpful start, you've still got a lot more work to do.