context menu creation in code - wpf

I am refactoring three related but different DataGrids from xaml into code and hitting an issue updating the header text of a context menu.
The command and text need to update according to which data grid cell is the current cell. The header text updated fine in xaml, but as you can see from the picture below, it now shows up as an empty string. The command itself does work properly, and works on the correct grid cell.
The setter for the header text fires property changed, but I suspect my code is not replicating the binding the way the xaml equivalent does. I'm also not sure if the Shared attribute is something I need account for in code.
Does anyone see how I can improve the code I am using?
Cheers,
Berryl
XAML style to establish bindings
<ContextMenu x:Key="NonProjectActivityContextMenu" x:Shared="true">
<MenuItem
DataContext="{Binding MakeEachWeekDayFullDayCommand}" Command="{Binding .}"
Header="{Binding HeaderText}" InputGestureText="{Binding InputGestureText}"
/>
<MenuItem
DataContext="{Binding MakeFullDayCommand}" Command="{Binding .}"
Header="{Binding HeaderText}" InputGestureText="{Binding InputGestureText}"
/>
</ContextMenu>
<!-- Bindings assumes a VmMenuItem (Command Reference) -->
<Style x:Key="ContextMenuItemStyle" TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding HeaderText}"/>
<Setter Property="InputGestureText" Value="{Binding InputGestureText}" />
<Setter Property="Command" Value="{Binding Command}" />
<Setter Property="Icon" Value="{Binding Icon}" />
<Setter Property="Tag" Value="{Binding IdTag}" />
<Setter Property="ItemsSource" Value="{Binding Children}"/>
</Style>
CODE
protected virtual ContextMenu _GetContextMenu() {
var menuItems = _dataContext.MenuItems.Select(menuItem => menuItem.ToMenuItem());
var cm = new ContextMenu();
foreach (var item in menuItems) {
cm.Items.Add(item);
}
return cm;
}
UPDATE
Well the empty string part was just my own stupidity - I hadn't initialized the header text! The picture below is what I get now, which is an improvement. The text should update to say the day of the week tho, ie, "Make Monday a full day"
EDIT for Erno
I am setting columns and the style for the grid itself as below, so I thought I can just fetch the resource for the context menu and set it.
Am getting an odd result however, as you can see from the pic - it's like the context menu is covering the whole grid!
private void OnDataGridLoaded(object sender, RoutedEventArgs e)
{
_dataContext = (ActivityCollectionViewModel)DataContext;
IsSynchronizedWithCurrentItem = true;
Style = (Style)FindResource(GRID_STYLE_NAME);
_AddColumns();
var timeSheetColumns = Columns.Cast<TimesheetGridColumn>();
foreach (var col in timeSheetColumns)
{
col.SetHeader();
col.SetCellStyle(this);
col.SetBinding();
}
if(DesignerProperties.GetIsInDesignMode(this)) {
// just so the designer doesn't hit a null reference on the data context
ItemsSource = new ObservableCollection<ActivityViewModel>();
}
else {
// ok, we have a runtime data context to work with
ItemsSource = _dataContext.ActivityVms;
InputBindings.AddRange(_GetKeyBindings());
ContextMenu = _GetContextMenu();
ContextMenu.Style = (Style)FindResource("ContextMenuItemStyle");
}
}
private void OnDataGridLoaded(object sender, RoutedEventArgs e)
{
_dataContext = (ActivityCollectionViewModel)DataContext;
IsSynchronizedWithCurrentItem = true;
Style = (Style)FindResource(GRID_STYLE_NAME);
_AddColumns();
var timeSheetColumns = Columns.Cast<TimesheetGridColumn>();
foreach (var col in timeSheetColumns)
{
col.SetHeader();
col.SetCellStyle(this);
col.SetBinding();
}
if(DesignerProperties.GetIsInDesignMode(this)) {
// just so the designer doesn't hit a null reference on the data context
ItemsSource = new ObservableCollection<ActivityViewModel>();
}
else {
// ok, we have a runtime data context to work with
ItemsSource = _dataContext.ActivityVms;
InputBindings.AddRange(_GetKeyBindings());
ContextMenu = _GetContextMenu();
ContextMenu.Style = (Style)FindResource("ContextMenuItemStyle");
}
}
Latest Update
I tried making my binding relative per this SO post but no dice. My command updated, meaning it executed on the correct cell, but I couldn't get the text to reflect which cell it was. I finally just decided to build the context menu on the fly as below. It work fine, although it seems I should have been able to do better.
Am going to give the answer to Erno and close this out.
private void OnCurrentCellChanged(object sender, EventArgs e)
{
if (ReferenceEquals(null, sender)) return;
var grid = (DataGrid)sender;
var selectedActivity = (ActivityViewModel)grid.CurrentItem;
if (ReferenceEquals(selectedActivity, null)) return;
if (_isEditableDayOfTheWeekColumn(grid.CurrentColumn))
{
var dowCol = (DayOfTheWeekColumn)grid.CurrentColumn;
var index = Convert.ToInt32(dowCol.DowIndex);
selectedActivity.SetSelectedAllocationVm(index);
}
else
{
selectedActivity.SetSelectedAllocationVm(-1);
}
var commands = selectedActivity
.AllCommands
.Select(vmMenuItem => vmMenuItem.Command.ToMenuItem());
var cm = new ContextMenu();
foreach (var item in commands)
{
//item.SetResourceReference(StyleProperty, "ContextMenuItemStyle");
cm.Items.Add(item);
}
grid.ContextMenu = cm;
}

My guess is you want to use a Style in code as well.
Just create an instance of the Style class, set its properties (including the binding) and add it to a Resources property in the tree.
Next, use the style in the generated menu items.

Related

(WPF DataGrid) How to copy Datagrid header text with a context menu?

I'm using WPF DataGrid.
I added a context menu to the column header, but i don't know how can I copy the header text in a menu item click event.
I try to use DataGrid.CurrentColumn but it is null
Thanks a lot !
try to get columns header by get it using the selected cells:
foreach (var item in e.SelectedCells)
{
DataGridRow row = (DataGridRow)dg.ItemContainerGenerator.ContainerFromItem(item.Item);
var col = item.Column as DataGridColumn;
MessageBox.Show("" + col.Header);
}
EDIT
if you want to get the column header that you are currently on add the following codes:
in resource put this:
<ContextMenu x:Key="cm">
<MenuItem Header="Click"
Click="mi_Click"
CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Parent}" />
</ContextMenu>
then add column header style like this:
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="ContextMenu" Value="{StaticResource cm}"></Setter>
</Style>
and put code behind like this:
private void mi_Click(object sender, RoutedEventArgs e)
{
MenuItem mi = sender as MenuItem;
if (mi != null)
{
ContextMenu cm = mi.CommandParameter as ContextMenu;
if (cm != null)
{
var ch = cm.PlacementTarget as System.Windows.Controls.Primitives.DataGridColumnHeader;
if (ch != null)
{
MessageBox.Show(ch.Content.ToString());
}
}
}
}

Reusable style for DataGridTextColumn in WPF [duplicate]

I tried to create a Style for DataGridTextColumn with the following code
<Style TargetType="{x:Type DataGridTextColumn}">
...
</Style>
However, Visual Studio 2010 highlights {x:Type DataGridTextColumn} with a blue line and elaborates: Exception has been thrown by the target of an invocation.
Why does this happen and how do I fix it?
You can't style the DataGridTextColumn because DataGridTextColumn does not derive from FrameworkElement (or FrameworkContentElement). Only FrameworkElement, etc supports styling.
When you attempt to create a style in XAML for any type that is not a FrameworkElement or FrameworkContentElement you get that error message.
How do you solve this? As with any problem, where there is a will there is a way. In this case I think the easiest solution is to create an attached property for DataGrid to assign a DataGridColumn style:
<DataGrid ...>
<local:MyDataGridHelper.TextColumnStyle>
<Style TargetType="FrameworkElement">
... setters here ...
</Style>
</local:MyDataGridHelper.TextColumnStyle>
...
The implementation would be something along these lines:
public class MyDataGridHelper : DependencyObject
{
// Use propa snipped to create attached TextColumnStyle with metadata:
... RegisterAttached("TextColumnStyle", typeof(Style), typeof(MyDataGridHelper), new PropertyMetadata
{
PropertyChangedCallback = (obj, e) =>
{
var grid = (DataGrid)obj;
if(e.OldValue==null && e.NewValue!=null)
grid.Columns.CollectionChanged += (obj2, e2) =>
{
UpdateColumnStyles(grid);
}
}
}
private void UpdateStyles(DataGrid grid)
{
var style = GetTextColumnStyle(grid);
foreach(var column in grid.Columns.OfType<DataGridTextColumn>())
foreach(var setter in style.Setters.OfType<Setter>())
if(setter.Value is BindingBase)
BindingOperations.SetBinding(column, setter.Property, setter.Value);
else
column.SetValue(setter.Property, setter.Value);
}
}
The way this works is, any time the attached property is changed, a handler is added for the Columns.CollectionChanged event on the grid. When the CollectionChanged event fires, all columns are updated with the style that was set.
Note that the above code does not handle the situation where a style is removed and re-added gracefully: Two event handlers are registered. For a really robust solution you would want to fix this by adding another attached property containing the event handler so the event handler could be unregistered, but for your purpose I think this is unimportant.
Another caveat here is that the direct use of SetBinding and SetValue will cause the DependencyProperty to have a BaseValueSource of Local instead of DefaultStyle. This will probably make no difference in your case but I thought I should mention it.
The style tag has to go in the right place. Your datagrid may look something like this right now:
<DataGrid>
<DataGrid.Columns>
<DataGridTextColumn />
</DataGrid.Columns>
</DataGrid>
You might initially try to add the style tag directly within the DataGridTextColumn element which will not work. You can however create elements for "DataGridTextColumn.ElementStyle" and or "DataGridTextColumn.EditingElementStyle" just within the "DataGridTextColumn" element. Each of those element tags can then have style tags within them:
<DataGrid>
<DataGrid.Columns>
<DataGridTextColumn>
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="Background" Value="Green"></Setter>
</Style>
</DataGridTextColumn.ElementStyle>
<DataGridTextColumn.EditingElementStyle>
<Style TargetType="TextBox">
<Setter Property="Background" Value="Orange"></Setter>
</Style>
</DataGridTextColumn.EditingElementStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
One style will be applied to viewing and the other will be applied when the cell is in edit mode. Note that it changes from a TextBlock when viewing to a TextBox when editing (This got me at first!).
This is more an addition to Ray Burns answer. I first wasn't able to implement it on my own, but with help of mm8 (https://stackoverflow.com/a/46690951/5381620) I got it running. Works really fine. For other people who have problems following this attached property approach maybe a full code snippet is helpful.
public class MyDataGridHelper : DependencyObject
{
private static readonly DependencyProperty TextColumnStyleProperty = DependencyProperty.RegisterAttached("TextColumnStyle", typeof(Style), typeof(MyDataGridHelper), new PropertyMetadata
{
PropertyChangedCallback = (obj, e) =>
{
var grid = (DataGrid)obj;
if (e.OldValue == null && e.NewValue != null)
grid.Columns.CollectionChanged += (obj2, e2) =>
{
UpdateColumnStyles(grid);
};
}
});
public static void SetTextColumnStyle(DependencyObject element, Style value)
{
element.SetValue(TextColumnStyleProperty, value);
}
public static Style GetTextColumnStyle(DependencyObject element)
{
return (Style)element.GetValue(TextColumnStyleProperty);
}
private static void UpdateColumnStyles(DataGrid grid)
{
var origStyle = GetTextColumnStyle(grid);
foreach (var column in grid.Columns.OfType<DataGridTextColumn>())
{
//may not add setters to a style which is already in use
//therefore we need to create a new style merging
//original style with setters from attached property
var newStyle = new Style();
newStyle.BasedOn = column.ElementStyle;
newStyle.TargetType = origStyle.TargetType;
foreach (var setter in origStyle.Setters.OfType<Setter>())
{
newStyle.Setters.Add(setter);
}
column.ElementStyle = newStyle;
}
}
}
xaml
<Grid>
<DataGrid Name="MyDataGrid" ItemsSource="{Binding Lines}" AutoGenerateColumns="False" >
<local:MyDataGridHelper.TextColumnStyle>
<Style TargetType="TextBlock">
<Setter Property="TextWrapping" Value="Wrap"/>
</Style>
</local:MyDataGridHelper.TextColumnStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="ProductId1" Binding="{Binding Path=Result1}" />
<DataGridTextColumn Header="ProductId2" Binding="{Binding Path=Result2}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
Edit: In first approach I did overwrite the whole style. In new version it is still possible to maintain other styles modifications like this one
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="Red"/>
</Style>
</DataGridTextColumn.ElementStyle>
A small addition to pedrito answer. Everything works fine, but if origStyle has base style, base style's setters get discarded.
To fix this, we need get all setters:
private static void UpdateColumnStyles(DataGrid grid)
{
var origStyle = GetTextColumnStyle(grid);
foreach (var column in grid.Columns.OfType<DataGridTextColumn>())
{
//may not add setters to a style which is already in use
//therefore we need to create a new style merging
//original style with setters from attached property
var newStyle = new Style();
newStyle.BasedOn = column.ElementStyle;
newStyle.TargetType = origStyle.TargetType;
var baseSetters = GetBaseSetters(origStyle);
var allSetters = baseSetters.Concat(origStyle.Setters.OfType<Setter>());
foreach (var setter in allSetters)
{
newStyle.Setters.Add(setter);
}
column.ElementStyle = newStyle;
}
}
private static IEnumerable<Setter> GetBaseSetters(Style style)
{
return style.BasedOn?.Setters.OfType<Setter>().Concat(GetBaseSetters(style.BasedOn)??new Setter[0]);
}
More simple:
<FontFamily x:Key="DefaultFont">Snap ITC</FontFamily>
<Style x:Key="ControlStyle" TargetType="Control">
<Setter Property="FontFamily" Value="{StaticResource DefaultFont}"/>
</Style>
<Style TargetType="{x:Type DataGridCellsPresenter}" BasedOn="{StaticResource ControlStyle}">
</Style>
A DataGridTextColumn is nothing but a column with a TextBlock in it. Write a style with the TargetType as TextBlock and bind the ElementStyle property of the DataGridTextColumn to it. Hope that helps!

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 to perform Single click checkbox selection in WPF DataGrid?

I have a DataGrid with first column as text column and second column as CheckBox column. What I want is, if I click the check box. It should get checked.
But, it takes two click to get selected, for first click the cell is getting selected, for the second clicks the check box is getting checked. How to make the check box to get checked/unchecked with a single click.
I'm using WPF 4.0. Columns in the DataGrid are AutoGenerated.
For single click DataGrid checkbox you can just put regular checkbox control inside DataGridTemplateColumn and set UpdateSourceTrigger=PropertyChanged.
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=IsSelected, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
I solved this with the following Style:
<Style TargetType="DataGridCell">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="IsEditing" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
It's of course possible to adapt this further for specific columns ...
First of, I know this is a pretty old question but I still thought I'd try and answer it.
I had the same problem a couple of days ago and came across a surprisingly short solution for it (see this blog). Basically, all you need to do is replace the DataGridCheckBoxColumn definition in your XAML with the following:
<DataGridTemplateColumn Header="MyCheckBoxColumnHeader">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox HorizontalAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding Path=MyViewModelProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
The upside of this solution is obvious - it's XAML-only; thus it effectively refrains your from burdening your code-behind with additional UI logic.
To make Konstantin Salavatov's answer work with AutoGenerateColumns, add an event handler to the DataGrid's AutoGeneratingColumn with the following code:
if (e.Column is DataGridCheckBoxColumn && !e.Column.IsReadOnly)
{
var checkboxFactory = new FrameworkElementFactory(typeof(CheckBox));
checkboxFactory.SetValue(FrameworkElement.HorizontalAlignmentProperty, HorizontalAlignment.Center);
checkboxFactory.SetValue(FrameworkElement.VerticalAlignmentProperty, VerticalAlignment.Center);
checkboxFactory.SetBinding(ToggleButton.IsCheckedProperty, new Binding(e.PropertyName) { UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged });
e.Column = new DataGridTemplateColumn
{
Header = e.Column.Header,
CellTemplate = new DataTemplate { VisualTree = checkboxFactory },
SortMemberPath = e.Column.SortMemberPath
};
}
This will make all of DataGrid's auto-generated checkbox columns be "single click" editable.
Based on blog referenced in Goblin's answer, but modified to work in .NET 4.0 and with Row-Selection Mode.
Notice that it also speeds up DataGridComboBoxColumn editing - by entering edit mode and displaying dropdown on single click or text input.
XAML:
<Style TargetType="{x:Type DataGridCell}">
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="DataGridCell_PreviewMouseLeftButtonDown" />
<EventSetter Event="PreviewTextInput" Handler="DataGridCell_PreviewTextInput" />
</Style>
Code-behind:
private void DataGridCell_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DataGridCell cell = sender as DataGridCell;
GridColumnFastEdit(cell, e);
}
private void DataGridCell_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
DataGridCell cell = sender as DataGridCell;
GridColumnFastEdit(cell, e);
}
private static void GridColumnFastEdit(DataGridCell cell, RoutedEventArgs e)
{
if (cell == null || cell.IsEditing || cell.IsReadOnly)
return;
DataGrid dataGrid = FindVisualParent<DataGrid>(cell);
if (dataGrid == null)
return;
if (!cell.IsFocused)
{
cell.Focus();
}
if (cell.Content is CheckBox)
{
if (dataGrid.SelectionUnit != DataGridSelectionUnit.FullRow)
{
if (!cell.IsSelected)
cell.IsSelected = true;
}
else
{
DataGridRow row = FindVisualParent<DataGridRow>(cell);
if (row != null && !row.IsSelected)
{
row.IsSelected = true;
}
}
}
else
{
ComboBox cb = cell.Content as ComboBox;
if (cb != null)
{
//DataGrid dataGrid = FindVisualParent<DataGrid>(cell);
dataGrid.BeginEdit(e);
cell.Dispatcher.Invoke(
DispatcherPriority.Background,
new Action(delegate { }));
cb.IsDropDownOpen = true;
}
}
}
private static T FindVisualParent<T>(UIElement element) where T : UIElement
{
UIElement parent = element;
while (parent != null)
{
T correctlyTyped = parent as T;
if (correctlyTyped != null)
{
return correctlyTyped;
}
parent = VisualTreeHelper.GetParent(parent) as UIElement;
}
return null;
}
I've tried these suggestions, and plenty of others I've found on other sites, but none of them quite worked for me. In the end, I created the following solution.
I've created my own DataGrid-inherited control, and simply added this code to it:
public class DataGridWithNavigation : Microsoft.Windows.Controls.DataGrid
{
public DataGridWithNavigation()
{
EventManager.RegisterClassHandler(typeof(DataGridCell),
DataGridCell.PreviewMouseLeftButtonDownEvent,
new RoutedEventHandler(this.OnPreviewMouseLeftButtonDown));
}
private void OnPreviewMouseLeftButtonDown(object sender, RoutedEventArgs e)
{
DataGridCell cell = sender as DataGridCell;
if (cell != null && !cell.IsEditing && !cell.IsReadOnly)
{
DependencyObject obj = FindFirstControlInChildren(cell, "CheckBox");
if (obj != null)
{
System.Windows.Controls.CheckBox cb = (System.Windows.Controls.CheckBox)obj;
cb.Focus();
cb.IsChecked = !cb.IsChecked;
}
}
}
public DependencyObject FindFirstControlInChildren(DependencyObject obj, string controlType)
{
if (obj == null)
return null;
// Get a list of all occurrences of a particular type of control (eg "CheckBox")
IEnumerable<DependencyObject> ctrls = FindInVisualTreeDown(obj, controlType);
if (ctrls.Count() == 0)
return null;
return ctrls.First();
}
public IEnumerable<DependencyObject> FindInVisualTreeDown(DependencyObject obj, string type)
{
if (obj != null)
{
if (obj.GetType().ToString().EndsWith(type))
{
yield return obj;
}
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
foreach (var child in FindInVisualTreeDown(VisualTreeHelper.GetChild(obj, i), type))
{
if (child != null)
{
yield return child;
}
}
}
}
yield break;
}
}
What does all this do ?
Well, each time we click on any cell in our DataGrid, we see if the cell contains a CheckBox control within it. If it does, then we'll set the focus to that CheckBox and toggle it's value.
This seems to work for me, and is a nice, easily reusable solution.
It is disappointing that we need to write code to do this though. The explanation that the first mouse click (on a DataGrid's CheckBox) is "ignored" as WPF uses it to put the row into Edit mode might sound logical, but in the real-world, this goes against the way every real application works.
If a user sees a checkbox on their screen, they should be able to click on it once to tick/untick it. End of story.
Base on Jim Adorno answer and comments on his post, this is solution with MultiTrigger:
<Style TargetType="DataGridCell">
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsReadOnly" Value="False" />
<Condition Property="IsMouseOver" Value="True" />
</MultiTrigger.Conditions>
<Setter Property="IsEditing" Value="True" />
</MultiTrigger>
</Style.Triggers>
</Style>
There is a much simpler solution here.
<DataGridTemplateColumn MinWidth="20" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<CheckBox VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
If you use DataGridCheckBoxColumn to implement, first click is to focus, second click is to check.
But using DataGridTemplateColumn to implement needs one click only.
The difference of using DataGridComboboxBoxColumn and implementation by DataGridTemplateColumn is also similar.
Yet another simple solution is to add this style to your DataGridColumn.The body of your style can be empty.
<DataGridCheckBoxColumn>
<DataGridCheckBoxColumn.ElementStyle>
<Style TargetType="CheckBox">
</Style>
</DataGridCheckBoxColumn.ElementStyle>
</DataGridCheckBoxColumn>
I solved with this:
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Viewbox Height="25">
<CheckBox IsChecked="{Binding TheProperty, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
</Viewbox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
The checkbox active on single click!
<Style x:Key="StilCelula" TargetType="DataGridCell">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="IsEditing"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Converter={StaticResource CheckBoxColumnToEditingConvertor}}" />
</Trigger>
</Style.Triggers>
<Style>
Imports System.Globalization
Public Class CheckBoxColumnToEditingConvertor
Implements IValueConverter
Public Function Convert(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As CultureInfo) As Object Implements IValueConverter.Convert
Try
Return TypeOf TryCast(value, DataGridCell).Column Is DataGridCheckBoxColumn
Catch ex As Exception
Return Visibility.Collapsed
End Try
End Function
Public Function ConvertBack(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
Throw New NotImplementedException()
End Function
End Class
Here an approach with an own column class that is based on the default DataGridCheckBoxColumn class and can be used like the normal one. Just copy/paste.
public class DataGridCheckBoxColumn : System.Windows.Controls.DataGridCheckBoxColumn
{
private static Style _noFocusEditElementStyle;
static DataGridCheckBoxColumn()
{
ElementStyleProperty.OverrideMetadata(typeof(DataGridCheckBoxColumn), new FrameworkPropertyMetadata(NoFocusEditElementStyle));
EditingElementStyleProperty.OverrideMetadata(typeof(DataGridCheckBoxColumn), new FrameworkPropertyMetadata(NoFocusEditElementStyle));
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.Property.Name == nameof(IsReadOnly))
{
ElementStyle = IsReadOnly ? DefaultElementStyle : NoFocusEditElementStyle;
EditingElementStyle = IsReadOnly ? DefaultElementStyle : NoFocusEditElementStyle;
}
}
public static Style NoFocusEditElementStyle
{
get
{
if (_noFocusEditElementStyle == null)
{
Style style = new Style(typeof(System.Windows.Controls.CheckBox));
// When not in edit mode, the end-user should not be able to toggle the state
style.Setters.Add(new Setter(UIElement.FocusableProperty, false));
style.Setters.Add(new Setter(System.Windows.Controls.CheckBox.HorizontalAlignmentProperty, HorizontalAlignment.Center));
style.Setters.Add(new Setter(System.Windows.Controls.CheckBox.VerticalAlignmentProperty, VerticalAlignment.Top));
style.Seal();
_noFocusEditElementStyle = style;
}
return _noFocusEditElementStyle;
}
}
}
Usage with Read/Write Property:
<myNamespace:DataGridCheckBoxColumn Header="Name"
Binding="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
Usage with ReadOnly Property:
<myNamespace:DataGridCheckBoxColumn Header="Name"
IsReadOnly="True"
Binding="{Binding Name, Mode=OneWay}" />
Explanation:
The default column class has two style properties one for the edit mode and one for the view.
In case of ReadOnly we use the view style in both cases
In the other case in the edit mode we set the edit style
Instead of EditElementStyle I prefered a style where the checkbox does not get the focus (NoFocusEditElementStyle) since this behavior looks a little weird

Programmatically assigning a color to a row in DataGrid

I need to assign a color to the row I add at runtime to the DataTable. How can this be done?
You can handle the DataGrid's LoadingRow event to detect when a row is being added. In the event handler you can get a reference to the DataRow that was added to the DataTable that is acting as your ItemsSource. Then you can update the DataGridRow's color however you like.
void dataGrid_LoadingRow(object sender, Microsoft.Windows.Controls.DataGridRowEventArgs e)
{
// Get the DataRow corresponding to the DataGridRow that is loading.
DataRowView item = e.Row.Item as DataRowView;
if (item != null)
{
DataRow row = item.Row;
// Access cell values values if needed...
// var colValue = row["ColumnName1]";
// var colValue2 = row["ColumName2]";
// Set the background color of the DataGrid row based on whatever data you like from
// the row.
e.Row.Background = new SolidColorBrush(Colors.BlanchedAlmond);
}
}
To sign up for the event in XAML:
<toolkit:DataGrid x:Name="dataGrid"
...
LoadingRow="dataGrid_LoadingRow">
Or in C#:
this.dataGrid.LoadingRow += new EventHandler<Microsoft.Windows.Controls.DataGridRowEventArgs>(dataGrid_LoadingRow);
U can try this
In the XAML
<Window.Resources>
<Style TargetType="{x:Type DataGridRow}">
<Style.Setters>
<Setter Property="Background" Value="{Binding Path=StatusColor}"></Setter>
</Style.Setters>
</Style>
</Window.Resources>
In the datagrid
<DataGrid AutoGenerateColumns="False" CanUserAddRows="False" Name="dtgTestColor" ItemsSource="{Binding}" >
<DataGrid.Columns>
<DataGridTextColumn Header="Valor" Binding="{Binding Path=Valor}"/>
</DataGrid.Columns>
</DataGrid>
In the code i have a class with
public class ColorRenglon
{
public string Valor { get; set; }
public string StatusColor { get; set; }
}
When set the DataContext
dtgTestColor.DataContext = ColorRenglon;
dtgTestColor.Items.Refresh();
If u not set the color of the row the default value is Grey
u can try this sample
with this sample
List<ColorRenglon> test = new List<ColorRenglon>();
ColorRenglon cambiandoColor = new ColorRenglon();
cambiandoColor.Valor = "Aqui va un color";
cambiandoColor.StatusColor = "Red";
test.Add(cambiandoColor);
cambiandoColor = new ColorRenglon();
cambiandoColor.Valor = "Aqui va otro color";
cambiandoColor.StatusColor = "PaleGreen";
test.Add(cambiandoColor);
IMPORTANT : be sure to always assign defaults for rows that aren't being colored by a condition - or any other style.
See my answer to C# Silverlight Datagrid - Row Color Change.
PS. I am in Silverlight and haven't confirmed this behavior in WPF

Resources