WPF :: Styling the RibbonComboBox Differently Than The RibbonGallery - wpf

I have a RibbonComboBox that is used to set font sizes. It has a RibbonGallery that lists the various font sizes, displayed in the appropriate FontSize:
<r:RibbonComboBox DataContext="{x:Static vm:RibbonDataModel.FontSizeComboBoxData}"
SelectionBoxWidth="30">
<r:RibbonGallery MaxColumnCount="1"
Command="{Binding Command}"
CommandParameter="{Binding SelectedItem}">
<r:RibbonGallery.GalleryItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding}"
FontSize="{Binding}" />
</Grid>
</DataTemplate>
</r:RibbonGallery.GalleryItemTemplate>
</r:RibbonGallery>
</r:RibbonComboBox>
EDIT Here is my ViewModel:
public static RibbonDataModel
{
public static GalleryData<object> FontSizeComboBoxData
{
get
{
lock (LockObject)
{
const string key = "Font Size";
if (!DataCollection.ContainsKey(key))
{
var value = new GalleryData<object>
{
Command = HtmlDocumentCommands.ChangeFontSize,
Label = "Change Font Size",
ToolTipDescription = "Set the font to a specific size.",
ToolTipTitle = "Change Font Size",
};
var fontSizes = new GalleryCategoryData<object>();
var i = 9.0;
while (i <= 30)
{
fontSizes.GalleryItemDataCollection.Add(i);
i += 0.75;
}
value.CategoryDataCollection.Add(fontSizes);
DataCollection[key] = value;
}
return DataCollection[key] as GalleryData<object>;
}
}
}
}
Everything works as expected, but after I select an item from the gallery, it shows up in the RibbonComboBox with the same huge (or tiny) FontSize as it uses in the gallery.
How can I "reset" the FontSize of the selected item to the default when it's displayed in the RibbonComboBox?

The RibbonComboBox uses a ContentPresenter to show the item you select in the RibbonGallery.
Moreover the ContentPresenter adopts the same ItemTemplate that you declared in the RibbonGallery.
This is the "core" reason of your problem.
So you can choose between two solutions to workaround the problem.
FIRST SOLUTION (the fastest one)
You can simply set the IsEditable property of your RibbonComboBox to "true". In this way the RibbonComboBox replaces the ContentPresenter with a TextBox, without using any ItemTemplate. Then the font will have the right size.
SECOND SOLUTION (the best one IMHO)
Since the ItemTemplate is used at the same from both the RibbonComboBox's ContentPresenter and the RibbonGallery, it is the point where we can try to solve the problem. The olny difference is that when the DataTemplate is placed inside the RibbonGallery, its parent is a RibbonGalleryItem.
So if its parent is not a RibbonGalleryItem, you automatically know that the DataTemplate is placed inside the ContentPresenter.
You can handle this situation by writing a simple DataTrigger.
Let's see all in the code.
I wrote a simplified ViewModel:
namespace WpfApplication1
{
public class FontSizes
{
private static FontSizes instance = new FontSizes();
private List<double> values = new List<double>();
public FontSizes()
{
double i = 9.0;
while (i <= 30)
{
values.Add(i);
i += 0.75;
}
}
public IList<double> Values
{
get
{
return values;
}
}
public static FontSizes Instance
{
get
{
return instance;
}
}
}
}
Then this is my View:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ribbon="http://schemas.microsoft.com/winfx/2006/xaml/presentation/ribbon"
xmlns:vm="clr-namespace:WpfApplication1"
Title="Window1" Height="300" Width="300">
<Window.Resources />
<DockPanel>
<ribbon:RibbonComboBox Label="Select a font size:"
SelectionBoxWidth="62"
VerticalAlignment="Center">
<ribbon:RibbonGallery MaxColumnCount="1">
<ribbon:RibbonGalleryCategory DataContext="{x:Static vm:FontSizes.Instance}" ItemsSource="{Binding Path=Values, Mode=OneWay}">
<ribbon:RibbonGalleryCategory.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Name="tb" Text="{Binding}" FontSize="{Binding}" />
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ribbon:RibbonGalleryItem, AncestorLevel=1}}"
Value="{x:Null}">
<Setter TargetName="tb" Property="FontSize" Value="12" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ribbon:RibbonGalleryCategory.ItemTemplate>
</ribbon:RibbonGalleryCategory>
</ribbon:RibbonGallery>
</ribbon:RibbonComboBox>
</DockPanel>
</Window>
As you can see the DataTrigger is the "component" which makes the "dirty job".
Now you just need to make you your mind about which solution you prefer.

I would advise you to use the Fluent.Ribbon library instead of the Microsoft Ribbons (as they are very buggy, not well maintained and only support old styles, really trust me on this one it will just save you much trouble).
Then you simply can use this code:
<fluent:ComboBox Header="Font Size" ItemsSource="{Binding FontSizes}">
<fluent:ComboBox.ItemTemplate>
<ItemContainerTemplate>
<TextBlock FontSize="{Binding }" Text="{Binding }" />
</ItemContainerTemplate>
</fluent:ComboBox.ItemTemplate>
</fluent:ComboBox>
And get the desired result:

Related

WPF: How to hide specific tab item in TabControl that with DataTemplate bind to views

I have a WPF xaml TabControl defined as below, how to set Visibility to Collapsed for the tab item based on binding?
Current code:
<TabControl>
<DataTemplate DataType="{x:Type foo:FooViewModel}">
<foo:FooView/>
</DataTemplate>
<DataTemplate DataType="{x:Type bar:BarViewModel}">
<bar:BarView/>
</DataTemplate>
<TabControl/>
What I want to implement to only hide foo TabItem based on condition, but seems it cannot specify the target name?? E.g. hide the template which name is Foo. Thanks for your help, have a nice day.
<TabControl ItemsSource="{Binding ParentModel}">
<DataTemplate x:Name="Foo" DataType="{x:Type foo:FooViewModel}">
<foo:FooView/>
</DataTemplate>
<DataTemplate x:Name="Bar" DataType="{x:Type bar:BarViewModel}">
<bar:BarView/>
</DataTemplate>
<TabControl.ItemContinerStyle>
<Style TargetType="TabItem" 'e.g. TargetName="Foo"<==========='>
<Style.Triggers>
...Some trigger condition ignored here
<Setter Property="Visibility" Value="Collapsed" />
</Style.Triggers>
</Style>
<TabControl.ItemContinerStyle/>
<TabControl/>
Hiding the TabItem element is visually equivalent to removing the element.
Since you use data templating, the usual procedure would be to filter the source collection based on your condition to remove the data model. Removing the data model will result in the corresponding container (defined by the DataTemplate) not being rendered i.e. it will be hidden.
In WPF you always focus on the data models and not on their containers. It makes thinks a lot easier.
In the view model that defines the ParentModel property, you can define the filter by accessing the ICollectionView of the source collection:
private void ApplyFilter()
{
ICollectionView parentModelView = CollectionViewSource.GetDefaultView(this.ParentModel);
// Remove all items from the view that satisfy the condition
parentModelView.Filter = Predicate;
}
private bool Predicate(object collectionItem)
{
// TODO::Implement condition: return 'true' to hide the item from the view
}
If you want to filter the view directly (not recommended) you can access the ItemsControl.Items property to obtain the current ICollectionView:
private void OnMainWIndowLoaded(object sender, EventArgs e)
{
this.TabControl.Items.Filter = Predicate;
}
private bool Predicate(object collectionItem)
{
// TODO::Implement condition: return 'true' to hide the item from the view
}
Can't you simply control it on the element?
<TabControl>
<TabItem Header="Tabitem 1" Visibility="{Binding TabItem1Visibility, Converter={StaticResource BoolToVisibilityConverter}}"/>
<TabItem Header="Tabitem 2" />
<TabItem Header="Tabitem 3" />
</TabControl>
where TabItem1Visibility is a property in your VM:
private bool _tabItem1Visibility;
public bool TabItem1Visibility
{
get { return _tabItem1Visibility; }
set
{
_tabItem1Visibility = value;
OnPropertyChanged();
}
}
Notes:
BoolToVisibilityConverter's Convert method must return Collapse in case the value is false, not Hidden.
If you set TabItem1Visibility to false while the tab is selected, the content will still shown up, so you have to add SelectedItem="{Binding SelectedTab, Mode=TwoWay}" to TabControl and update it properly.

how to apply different font styles in one string

I have a wpf app, c# that displays texts (teahcer's name) when a combobox is closed. Basically it is contained in 1 string and being passed to the classInstance.TeachersComboBoxText or Text="{Binding Path=TeachersComboBoxText}" . Everything is great but the client is requesting for the text to be in italic if the teacher is flagged as not default or secondary teacher. Basically, I need to have a string but different font styles is applied to it,depends on my data.
<ComboBox Name="instanceTeachersComboBox"
IsEditable="True"
IsReadOnly="True"
ItemsSource="{Binding Path=TeacherList}" DropDownClosed="teachersComboBox_DropDownClosed"
Text="{Binding Path=TeachersComboBoxText}"
FontWeight="{Binding Path=IsSelectedFontWeight}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=IsSelected}"
Background="{Binding Path=IsActiveColour}"
Content="{Binding Path=Display}"
Foreground="{Binding Path=IsClashColour}"
FontStyle="{Binding Path=EndDateFontStyle}"/>
<Image Source="/SchoolEdgeTimetable;component/Images/greentick16x16.png"
Margin="5,0,0,0"
Visibility="{Binding Path=IsSpecialitySubjectVisibility}"></Image>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
=== this is the code ====
if (classInstance.TeacherList.Any(c => c.IsSelected == true && c.IsDefault == false) || instanceCount != defaultCount)
{
System.Windows.Forms.RichTextBox rtf = new System.Windows.Forms.RichTextBox();
foreach (var item in data)
{
if (!item.IsDefault)
{
rtf.SelectionFont = new Font("Arial", 12, System.Drawing.FontStyle.Italic);
rtf.AppendText(item.Display);
}
text = GT.Join2Strings(text, item.IsDefault? item.Display : rtf.Text, "\n");
}
}
else
text = DEFAULT_TEXT;
classInstance.TeachersComboBoxText = text;
I tried rtf because it seems that I can't apply it in plain string, but the code is not working. Is there any way to solve this without using rtf? or if not why is my code not working? anyone ca suggest a better way to do this will be appreciated. thanks
UPDATE:
People are suggesting to modify the template which I already did, that is working great in showing the items when a combobox is open. One of my items is RED and in italic as you can see BUT What I am trying to do though is modify display of the combobox TEXT property. I have attached an image to get a better view on what I mean.
thanks
It's pretty simple if you know how to utilise <Run> tag of TextBlock control in code behind. See below code:
//loop through all the teachers
foreach (var teacher in vm.Teachers)
{
//check if teacher is default
if (teacher.IsDefault)
{
//add text to "tb" textblock, in italic style
tb.Inlines.Add(new Run(teacher.Name) { FontStyle = FontStyles.Italic });
}
else
{
//add text to "tb" textblock
tb.Inlines.Add(new Run(teacher.Name));
}
}
If you are keen to know more about inline formatting, here is a cool blog post for same. Let me know if you need any more help on this.
You could handle the Loaded event for the CheckBox in the ItemTemplate and set its Content property to a TextBlock of Inline elements, e.g.:
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=IsSelected}"
Background="{Binding Path=IsActiveColour}"
Foreground="{Binding Path=IsClashColour}"
FontStyle="{Binding Path=EndDateFontStyle}"
Loaded="CheckBox_Loaded">
</CheckBox>
<Image Source="/SchoolEdgeTimetable;component/Images/greentick16x16.png"
Margin="5,0,0,0"
Visibility="{Binding Path=IsSpecialitySubjectVisibility}"></Image>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
private void CheckBox_Loaded(object sender, RoutedEventArgs e)
{
CheckBox checkBox = sender as CheckBox;
dynamic dataObject = checkBox.DataContext;
string display = dataObject.Display;
TextBlock textBlock = new TextBlock();
//split the display string and add Inlines to the TextBlock as suggested by #Naresh Ravlani here...
checkBox.Content = textBlock;
}
Add a datatrigger that changes your font to Italic:
<DataTrigger Binding="{Binding IsDefault}" Value="1">
<Setter Property="FontStyle" Value="Italic"/>
</DataTrigger>

How to change ContentControl's Content property using DataTemplate's DataTriggers based on Window's ViewModel property which is an enum value

I ask for your help in solving something that is simply said and most likely simply done but not for me at the moment.
What I'm developing is an application that looks like Office 2013 using FluidUI control set.
I want to achieve view switcher from Word/Access where on ribbon there is Views tab and there are buttons which switches views.
I thought that storing whole View object inside my ViewModel's property CurrentView is wrong way and I try to make this application as MVVM pure as possible. This application is more like "How to write app using MVVM" because I'm still learing WPF.
Ok. So I have my Window (MainWindowModern to be correct) which has Fluid Ribbon. There are 3 buttons to switch views (I call them Editors).
What they do is to change MainWindowModern's ViewModel's CurrentView property and set new enum value to it. This part of setting new enum value is done and works.
Now. The main body of the window is just ContentControl. Now I want to change this ContentControl's Content property based on DataContext.CurrentView property value. Like I said it before. I don't want to do any code-behind inside view's c# (i'm writing this app in C#) file.
The only thing that doesn't work is just changing ContentControl Content property. I'm trying to do this using DataTemplates and DataTemplate.Triggers
Now here's what I've got so far (without unrelated code).
The Window's XAML
<Fluent:MetroWindow
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Fluent="clr-namespace:Fluent;assembly=Fluent"
xmlns:localVM="clr-namespace:MVVMTest.ViewModels"
xmlns:local="clr-namespace:MVVMTest"
x:Class="MVVMTest.Views.MainWindowModern"
x:Name="ThisWindow"
Title="Dialogue Editor (Modern UI Version)"
Width="1280" Height="480"
RibbonThemeColor="Red" WindowState="Maximized"
Icon="..\Assets\App\AppIcon_32x32.png">
<Window.Resources>
<DataTemplate x:Key="CharactersEditorTemplate">
<TextBlock Text="Characters Editor Template Body" />
</DataTemplate>
<DataTemplate x:Key="ChaptersEditorTemplate">
<TextBlock Text="Chapters Editor Template Body" />
</DataTemplate>
<DataTemplate x:Key="ConversationsEditorTemplate">
<TextBlock Text="Conversations Editor Template Body" />
</DataTemplate>
<DataTemplate x:Key="aaa" DataType="{x:Type ContentControl}" >
<TextBlock Text="{Binding ElementName=ThisWindow, Path=DataContext.CurrentView}" />
<DataTemplate.Triggers>
<!--<DataTrigger Binding="{Binding CurrentView}">
<DataTrigger.Value>
<localVM:EditorView>CharactersEditor</localVM:EditorView>
</DataTrigger.Value>
<Setter Property="Content" Value="{StaticResource CharactersEditorTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding CurrentView}">
<DataTrigger.Value>
<localVM:EditorView>ChaptersEditor</localVM:EditorView>
</DataTrigger.Value>
<Setter Property="Content" Value="{StaticResource ChaptersEditorTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding CurrentView}">
<DataTrigger.Value>
<localVM:EditorView>ConversationsEditor</localVM:EditorView>
</DataTrigger.Value>
<Setter Property="Content" Value="{StaticResource ConversationsEditorTemplate}" />
</DataTrigger>-->
<DataTrigger Binding="{Binding ElementName=ThisWindow, Path=DataContext.CurrentView}">
<DataTrigger.Value>
<localVM:EditorView>ChaptersEditor</localVM:EditorView>
</DataTrigger.Value>
<DataTrigger.Setters>
<Setter Property="Content" Value="{StaticResource ChaptersEditorTemplate}" />
</DataTrigger.Setters>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<localVM:MainWindowVM />
</Window.DataContext>
<DockPanel x:Name="LayoutRoot" LastChildFill="True">
<Fluent:Ribbon DockPanel.Dock="Top">
<Fluent:RibbonTabItem Header="VIEW" Fluent:KeyTip.Keys="V" ReduceOrder="ViewsRibbonGroupBox, ViewsRibbonGroupBox, ViewsRibbonGroupBox">
<Fluent:RibbonGroupBox Name="ViewsRibbonGroupBox" Header="Views">
<Fluent:Button Name="CharactersViewButton"
Header="Characters"
LargeIcon="..\Assets\Ribbon\View\CharacterEditorIcon_32x32.png"
Icon="..\Assets\Ribbon\View\CharacterEditorIcon_32x32.png"
Command="{Binding SwitchToCharactersEditorCommand}" >
<Fluent:Button.ToolTip>
<Fluent:ScreenTip
Image="..\Assets\Ribbon\View\CharacterEditorIcon_32x32.png"
Title="Characters Editor"
Text="Changes current view to Characters Editor view.
In this view user can:
• List existing characters
• Create new characters
• Edit existing characters
• Delete existing characters
It is also possible to manage character's emotions in this view." />
</Fluent:Button.ToolTip>
</Fluent:Button>
<Fluent:Button Name="ChaptersViewButton"
Header="Chapters"
LargeIcon="..\Assets\Ribbon\View\ChapterEditorIcon_32x32.png"
Icon="..\Assets\Ribbon\View\ChapterEditorIcon_32x32.png"
Command="{Binding SwitchToChaptersEditorCommand}" >
<Fluent:Button.ToolTip>
<Fluent:ScreenTip
Image="..\Assets\Ribbon\View\ChapterEditorIcon_32x32.png"
Title="Chapters Editor"
Text="Changes current view to Chapters Editor view.
In this view user can:
• List existing chapters
• Create new chapters
• Edit existing chapters
• Delete existing chapters
It is also possible to manage chapters's missions in this view." />
</Fluent:Button.ToolTip>
</Fluent:Button>
<Fluent:Button Name="ConversationsViewButton"
Header="Conversations"
LargeIcon="..\Assets\Ribbon\View\ConversationEditorIcon_32x32.png"
Icon="..\Assets\Ribbon\View\ConversationEditorIcon_32x32.png"
Command="{Binding SwitchToConversationsEditorCommand}" >
<Fluent:Button.ToolTip>
<Fluent:ScreenTip
Image="..\Assets\Ribbon\View\ConversationEditorIcon_32x32.png"
Title="Conversations Editor"
Text="Changes current view to Conversations Editor view.
In this view user can:
• List existing conversations
• Create new conversations
• Edit existing conversations
• Delete existing conversations
It is also possible to manage conversations's statements and statement's stages in this view."
DisableReason="Please define at least one chapter with at least one mission in it to enable Conversations Editor.
Also it would be helpful to define at least one character with at least one emotion.
It is optional action but highly recommended." />
</Fluent:Button.ToolTip>
</Fluent:Button>
</Fluent:RibbonGroupBox>
</Fluent:RibbonTabItem>
</Fluent:Ribbon>
<ContentControl Name="MainContent" ContentTemplate="{StaticResource aaa}" />
</DockPanel>
</Fluent:MetroWindow>
Window's ViewModel and the enum
using MVVMTest.Models;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMTest.ViewModels {
public enum EditorView {
CharactersEditor,
ChaptersEditor,
ConversationsEditor
}
public class MainWindowVM : ViewModelBase {
public MainWindowVM() {
this.init();
}
protected void init() {
this.Characters = new ObservableCollection<CharacterVM>();
this.initCommands();
this.initSampleData();
}
protected void initSampleData() {
Character ch1 = new Character() { Name = "Character 1" };
Emotion e1 = new Emotion() { Name = "Emotion 1" };
ch1.Emotions.Add(e1);
CharacterVM ch1vm = new CharacterVM(ch1);
this.Characters.Add(ch1vm);
this.CurrentView = EditorView.ConversationsEditor;
}
protected void initCommands() {
this.SwitchToCharactersEditorCommand = new RelayCommand(param => this.SwitchToCharactersEditor(), param => this.CanSwitchToCharactersEditor());
this.SwitchToChaptersEditorCommand = new RelayCommand(param => this.SwitchToChaptersEditor(), param => this.CanSwitchToChaptersEditor());
this.SwitchToConversationsEditorCommand = new RelayCommand(param => this.SwitchToConversationsEditor(), param => this.CanSwitchToConversationsEditor());
}
public ObservableCollection<CharacterVM> Characters { get; set; }
protected EditorView _currentView;
public EditorView CurrentView {
get { return this._currentView; }
set {
if (this._currentView == value) {
return;
}
this._currentView = value;
this.OnPropertyChanged("CurrentView");
}
}
#region Commands
#region View Tab
#region Switch To Characters Editor
public RelayCommand SwitchToCharactersEditorCommand { get; private set; }
protected void SwitchToCharactersEditor() {
this.CurrentView = EditorView.CharactersEditor;
}
protected bool CanSwitchToCharactersEditor() {
if (this.CurrentView != EditorView.CharactersEditor) {
return true;
}
return false;
}
#endregion Switch To Characters Editor
#region Switch To Chapters Editor
public RelayCommand SwitchToChaptersEditorCommand { get; private set; }
protected void SwitchToChaptersEditor() {
this.CurrentView = EditorView.ChaptersEditor;
}
protected bool CanSwitchToChaptersEditor() {
if (this.CurrentView != EditorView.ChaptersEditor) {
return true;
}
return false;
}
#endregion Switch To Chapters Editor
#region Switch To Conversations Editor
public RelayCommand SwitchToConversationsEditorCommand { get; private set; }
protected void SwitchToConversationsEditor() {
this.CurrentView = EditorView.ConversationsEditor;
}
protected bool CanSwitchToConversationsEditor() {
if (this.CurrentView != EditorView.ConversationsEditor) {
return true;
}
return false;
}
#endregion Switch To Conversations Editor
#endregion View Tab
#endregion Commands
}
}
When this is all done the next step is to add animation to view switch just like on ModernUI Apps (or android smartphones) so the old content goes over the border of the window and new content comes from the other side. If it is not possible then I'll stop with just working switcher.
The good news is that this is actually pretty easy to do, including the animation switching. What you need is an ItemsControl to host your subviews. The ItemsControl allows you to use a DataTemplateSelector. So based on your enum value you can produce some output that the selector can use to determine which datatemplate to use. Try some research on the selector. If your are still confused feel free to reach out to me. Good luck.
For animations, I suggest a container control that would host your subviews. Check out this link for a pretty solid implementation to get you started.

How do I bind a Ribbon control to my ViewModel?

I've been struggling since yesterday to build a (what I thought) simple Ribbon on WPF, using MVVM.
I found quite a few links on the internet (and on Stack Overflow), but none could really solve my problem.
In a nutshell my question is: Am I doing it wrong, or is it just plain impossible to bind a Ribbon to a ViewModel like I'm doing?
In Details:
This is my model for the menu: Each MenuGroup should be rendered as a RibbonGroup, and MenuGroup contains a collection of MenuItem which should be rendered as a RibbonButton (inside their respective RibbonGroup of course).
using Caliburn.Micro;
namespace MyCompany.Poc.Wpf.Models
{
public class MenuGroup
{
public MenuGroup()
{
Items = new BindableCollection<MenuItem>();
}
public string Name { get; set; }
public IObservableCollection<MenuItem> Items { get; set; }
}
}
using System.Windows.Input;
namespace MyCompany.Poc.Wpf.Models
{
public class MenuItem
{
public MenuGroup Group { get; set; }
public string Name { get; set; }
public string Link { get; set; }
public ICommand OpenMenuUrl { get; set; }
}
}
My ViewModel just contains a collection of those MenuGroup, for the binding on the XAML:
public IObservableCollection<MenuGroup> LegacyMenuItems
{
get { return _legacyMenuItems; }
}
The collection is populated (for the time being) as soon as the VM is instanciated, with fake data):
var group1 = new MenuGroup();
group1.Name = "Group 1";
var group2 = new MenuGroup();
group2.Name = "Group 2";
group1.Items.Add(new MenuItem {Group = group1, Link = "http://www.google.co.uk", Name = "Link 1", OpenMenuUrl = OpenMenuUrl});
group1.Items.Add(new MenuItem {Group = group1, Link = "http://www.google.co.uk", Name = "Link 2", OpenMenuUrl = OpenMenuUrl});
group2.Items.Add(new MenuItem {Group = group2, Link = "http://www.google.co.uk", Name = "Link 3", OpenMenuUrl = OpenMenuUrl});
group2.Items.Add(new MenuItem {Group = group2, Link = "http://www.google.co.uk", Name = "Link 4", OpenMenuUrl = OpenMenuUrl});
LegacyMenuItems.Add(group1);
LegacyMenuItems.Add(group2);
Now the XAML:
<my:Ribbon DockPanel.Dock="Top">
<my:RibbonTab Header="Legacy A.I." ItemsSource="{Binding LegacyMenuItems}">
<my:RibbonTab.ItemTemplate>
<DataTemplate>
<my:RibbonGroup Header="{Binding Name}" ItemsSource="{Binding Items}">
<my:RibbonGroup.ItemTemplate>
<DataTemplate>
<my:RibbonButton Label="{Binding Name}" Command="{Binding OpenMenuUrl}" CommandParameter="{Binding Link}"></my:RibbonButton>
</DataTemplate>
</my:RibbonGroup.ItemTemplate>
</my:RibbonGroup>
</DataTemplate>
</my:RibbonTab.ItemTemplate>
</my:RibbonTab>
<my:RibbonTab Header="Static">
<my:RibbonGroup Name="Administration" Header="Admin">
<my:RibbonButton Label="Stuffs" Command="{Binding Path=OpenMenuUrl}" CommandParameter="http://www.mycompany.com/somelink"></my:RibbonButton>
<my:RibbonButton Label="Google" Command="{Binding Path=OpenMenuUrl}" CommandParameter="http://www.google.co.uk"></my:RibbonButton>
</my:RibbonGroup>
</my:RibbonTab>
</my:Ribbon>
And what it renders:
As you can see, the "Static" tab works well and the "title" for the ribbon group ("Admin") is displayed at the right place.
Now, the "Legacy A.I." tab, has no buttons (there should be 2 buttons in each group), and the RibbonGroup titles are displayed funny (below where they should be).
If you have any clue on what I'm doing wrong here, please tell me :)
I'm very new on the WPF world, so I obviously don't understand templating properly...
A few facts to help you make a call:
- The Model is loaded correctly, since we can see "Group 1" and "Group 2" which are part of the fake data loaded in the ViewModel
- I tried Telerik's RadRibbon and it behaves exactly the same! So unless they are BOTH wrong at the same place, the problem must comes from me
Good night and good luck :)
I am using the ribbon also in a MVVM application and decided to stick with controls defined in XAML linking them to static ICommand properties in classes defined in my viewmodel layer. This is very inflexible but works for me so far.
If you have downloaded and installed the official Microsoft Ribbon you will find it's source and some sample's in:
C:\Program Files\Microsoft Ribbon for
WPF\MicrosoftRibbonForWPFSourceAndSamples
This includes a sample that also implements MVVM. This uses a totally different method though an may not be what you need, but I think it may be helpful to look it over.
I had the same problem yesterday and struggled a lot to find a solution.
I think your problem could be solved with a HierarchicalDataTemplate.
<r:Ribbon>
<r:RibbonTab Header="Legacy A.I." ItemsSource="{Binding LegacyMenuItems}">
<r:RibbonTab.Style>
<Style TargetType="{x:Type r:RibbonTab}">
<Setter Property="ItemsSource" Value="{Binding LegacyMenuItems}" />
<Setter Property="ItemTemplate">
<Setter.Value>
<HierarchicalDataTemplate DataType = "{x:Type r:RibbonGroup}"
ItemsSource = "{Binding Items}" >
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</Setter.Value>
</Setter>
</Style>
</r:RibbonTab.Style>
</r:RibbonTab>
</r:Ribbon>
Captain Necroposter here:
I had the same rendering issue and this is my solution
<HierarchicalDataTemplate DataType="{x:Type navigation:RibbonTabViewModel}" ItemsSource="{Binding Path=Groups}">
<TextBlock Text="{Binding Path=Title}"></TextBlock>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type navigation:RibbonGroupViewModel}" ItemsSource="{Binding Path=Items}">
<TextBlock Text="{Binding Path=Title}" ></TextBlock>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type navigation:RibbonButtonViewModel}">
<RibbonButton Label="{Binding Path=Title}" Command="{Binding Path=ClickedCommand}" CommandParameter="{Binding Path=.}"></RibbonButton>
</DataTemplate>
To begin with i had a RibbonGroup as template content for my RibbonGroupViewModel. Thanks to latest VS i was able to inspect the live tree and find the issue there.

WPF DataGrid: Blank Row Missing

I am creating a WPF window with a DataGrid, and I want to show the blank "new item" row at the bottom of the grid that allows me to add a new item to the grid. For some reason, the blank row is not shown on the grid on my window. Here is the markup I used to create the DataGrid:
<toolkit:DataGrid x:Name="ProjectTasksDataGrid"
DockPanel.Dock="Top"
Style="{DynamicResource {x:Static res:SharedResources.FsBlueGridKey}}"
AutoGenerateColumns="False"
ItemsSource="{Binding SelectedProject.Tasks}"
RowHeaderWidth="0"
MouseMove="OnStartDrag"
DragEnter="OnCheckDropTarget"
DragOver="OnCheckDropTarget"
DragLeave="OnCheckDropTarget"
Drop="OnDrop"
InitializingNewItem="ProjectTasksDataGrid_InitializingNewItem">
<toolkit:DataGrid.Columns>
<toolkit:DataGridCheckBoxColumn HeaderTemplate="{DynamicResource {x:Static res:SharedResources.CheckmarkHeaderKey}}" Width="25" Binding="{Binding Completed}" IsReadOnly="false"/>
<toolkit:DataGridTextColumn Header="Days" Width="75" Binding="{Binding NumDays}" IsReadOnly="false"/>
<toolkit:DataGridTextColumn Header="Due Date" Width="75" Binding="{Binding DueDate, Converter={StaticResource standardDateConverter}}" IsReadOnly="false"/>
<toolkit:DataGridTextColumn Header="Description" Width="*" Binding="{Binding Description}" IsReadOnly="false"/>
</toolkit:DataGrid.Columns>
</toolkit:DataGrid>
I can't figure out why the blank row isn't showing. I have tried the obvious stuff (IsReadOnly="false", CanUserAddRows="True"), with no luck. Any idea why the blank row is disabled? Thanks for your help.
You must also have to have a default constructor on the type in the collection.
Finally got back to this one. I am not going to change the accepted answer (green checkmark), but here is the cause of the problem:
My View Model wraps domain classes to provide infrastructure needed by WPF. I wrote a CodeProject article on the wrap method I use, which includes a collection class that has two type parameters:
VmCollection<VM, DM>
where DM is a wrapped domain class, and DM is the WPF class that wraps it.
It truns out that, for some weird reason, having the second type parameter in the collection class causes the WPF DataGrid to become uneditable. The fix is to eliminate the second type parameter.
Can't say why this works, only that it does. Hope it helps somebody else down the road.
Vincent Sibal posted an article describing what is required for adding new rows to a DataGrid. There are quite a few possibilities, and most of this depends on the type of collection you're using for SelectedProject.Tasks.
I would recommend making sure that "Tasks" is not a read only collection, and that it supports one of the required interfaces (mentioned in the previous link) to allow new items to be added correctly with DataGrid.
In my opinion this is a bug in the DataGrid. Mike Blandford's link helped me to finally realize what the problem is: The DataGrid does not recognize the type of the rows until it has a real object bound. The edit row does not appear b/c the data grid doesn't know the column types. You would think that binding a strongly typed collection would work, but it does not.
To expand upon Mike Blandford's answer, you must first assign the empty collection and then add and remove a row. For example,
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// data binding
dataGridUsers.ItemsSource = GetMembershipUsers();
EntRefUserDataSet.EntRefUserDataTable dt = (EntRefUserDataSet.EntRefUserDataTable)dataGridUsers.ItemsSource;
// hack to force edit row to appear for empty collections
if (dt.Rows.Count == 0)
{
dt.AddEntRefUserRow("", "", false, false);
dt.Rows[0].Delete();
}
}
Add an empty item to your ItemsSource and then remove it. You may have to set CanUserAddRows back to true after doing this. I read this solution here: (Posts by Jarrey and Rick Roen)
I had this problem when I set the ItemsSource to a DataTable's DefaultView and the view was empty. The columns were defined though so it should have been able to get them. Heh.
This happned to me , i forgot to new up the instance and it was nightmare for me . once i created an instance of the collection in onviewloaded it was solved.
`observablecollection<T> _newvariable = new observablecollection<T>();`
this solved my problem. hope it may help others
For me the best way to implement editable asynchronous DataGrid looks like that:
View Model:
public class UserTextMainViewModel : ViewModelBase
{
private bool _isBusy;
public bool IsBusy
{
get { return _isBusy; }
set
{
this._isBusy = value;
OnPropertyChanged();
}
}
private bool _isSearchActive;
private bool _isLoading;
private string _searchInput;
public string SearchInput
{
get { return _searchInput; }
set
{
_searchInput = value;
OnPropertyChanged();
_isSearchActive = !string.IsNullOrEmpty(value);
ApplySearch();
}
}
private ListCollectionView _translationsView;
public ListCollectionView TranslationsView
{
get
{
if (_translationsView == null)
{
OnRefreshRequired();
}
return _translationsView;
}
set
{
_translationsView = value;
OnPropertyChanged();
}
}
private void ApplySearch()
{
var view = TranslationsView;
if (view == null) return;
if (!_isSearchActive)
{
view.Filter = null;
}
else if (view.Filter == null)
{
view.Filter = FilterUserText;
}
else
{
view.Refresh();
}
}
private bool FilterUserText(object o)
{
if (!_isSearchActive) return true;
var item = (UserTextViewModel)o;
return item.Key.Contains(_searchInput, StringComparison.InvariantCultureIgnoreCase) ||
item.Value.Contains(_searchInput, StringComparison.InvariantCultureIgnoreCase);
}
private ICommand _clearSearchCommand;
public ICommand ClearSearchCommand
{
get
{
return _clearSearchCommand ??
(_clearSearchCommand =
new DelegateCommand((param) =>
{
this.SearchInput = string.Empty;
}, (p) => !string.IsNullOrEmpty(this.SearchInput)));
}
}
private async void OnRefreshRequired()
{
if (_isLoading) return;
_isLoading = true;
IsBusy = true;
try
{
var result = await LoadDefinitions();
TranslationsView = new ListCollectionView(result);
}
catch (Exception ex)
{
//ex.HandleError();//TODO: Needs to create properly error handling
}
_isLoading = false;
IsBusy = false;
}
private async Task<IList> LoadDefinitions()
{
var translatioViewModels = await Task.Run(() => TranslationRepository.Instance.AllTranslationsCache
.Select(model => new UserTextViewModel(model)).ToList());
return translatioViewModels;
}
}
XAML:
<UserControl x:Class="UCM.WFDesigner.Views.UserTextMainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:model="clr-namespace:Cellebrite.Diagnostics.Model.Entities;assembly=Cellebrite.Diagnostics.Model"
xmlns:System="clr-namespace:System;assembly=mscorlib"
xmlns:converters1="clr-namespace:UCM.Infra.Converters;assembly=UCM.Infra"
xmlns:core="clr-namespace:UCM.WFDesigner.Core"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="300">
<DockPanel>
<StackPanel Orientation="Horizontal"
DockPanel.Dock="Top"
HorizontalAlignment="Left">
<DockPanel>
<TextBlock Text="Search:"
DockPanel.Dock="Left"
VerticalAlignment="Center"
FontWeight="Bold"
Margin="0,0,5,0" />
<Button Style="{StaticResource StyleButtonDeleteCommon}"
Height="20"
Width="20"
DockPanel.Dock="Right"
ToolTip="Clear Filter"
Command="{Binding ClearSearchCommand}" />
<TextBox Text="{Binding SearchInput, UpdateSourceTrigger=PropertyChanged}"
Width="500"
VerticalContentAlignment="Center"
Margin="0,0,2,0"
FontSize="13" />
</DockPanel>
</StackPanel>
<Grid>
<DataGrid ItemsSource="{Binding Path=TranslationsView}"
AutoGenerateColumns="False"
SelectionMode="Single"
CanUserAddRows="True">
<DataGrid.Columns>
<!-- your columns definition is here-->
</DataGrid.Columns>
</DataGrid>
<!-- your "busy indicator", that shows to user a message instead of stuck data grid-->
<Border Visibility="{Binding IsBusy,Converter={converters1:BooleanToSomethingConverter TrueValue='Visible', FalseValue='Collapsed'}}"
Background="#50000000">
<TextBlock Foreground="White"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Text="Loading. . ."
FontSize="16" />
</Border>
</Grid>
</DockPanel>
This pattern allows to work with data grid in a quite simple way and code is very simple either.
Do not forget to create default constructor for class that represents your data source.

Resources