Override Telerik Close Button - c#

I have a telerik close button on a dock that I am wanting to override the style of to allow the user to make a choice of what they want to close.
Take Notepad++ for example.. There is a "Close" and a "Close all BUT this" option.
That is exactly what I am wanting to do with this telerik radDock close button.
I have researched this and could not find anything helpful enough to really get me started. I just started using WPF (and really C#) so any helpful advice, code, or sample projects would be appreciated. Thank you in advanced.
Metro Smurf, it is pretty much exactly like the tutorial at this point. I am pretty new to WPF and C# so please be nice haha.
Here is my XAML:
<Window x:Class="RadDockCloseButton1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
xmlns:local="clr-namespace:Telerik.Windows.Controls;assembly=Telerik.Windows.Controls.Docking"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="ContextMenuTemplate">
<telerik:RadContextMenu InheritDataContext="False">
<telerik:RadMenuItem
IsChecked="{Binding IsFloatingOnly}"
Command="telerik:RadDockingCommands.Floating"
CommandParameter="{Binding}"
CommandTarget="{Binding}"
Header="{Binding Command.Text, RelativeSource={RelativeSource Self}}"/>
<telerik:RadMenuItem
IsChecked="{Binding IsDockableOptionChecked}"
Command="telerik:RadDockingCommands.Dockable"
CommandParameter="{Binding}"
CommandTarget="{Binding}"
Header="{Binding Command.Text, RelativeSource={RelativeSource Self}}" />
<telerik:RadMenuItem
Command="local:RadDockingCommands.CloseAllButThisCommand"
CommandParameter="{Binding}"
CommandTarget="{Binding}"
Header="{Binding Command.Text, RelativeSource={RelativeSource Self}}" />
</telerik:RadContextMenu>
</DataTemplate>
<Style TargetType="telerik:RadPane">
<Setter Property="ContextMenuTemplate" Value="{StaticResource ContextMenuTemplate}" />
</Style>
</Window.Resources>
<Grid>
<telerik:RadDocking x:Name="radDocking">
<telerik:RadDocking.DocumentHost>
<telerik:RadSplitContainer>
<telerik:RadPaneGroup x:Name="radPaneGroup">
<telerik:RadPane TitleTemplate="{StaticResource ContextMenuTemplate}" Title="Pane 1">
<TextBlock Text="Some simple text here"/>
</telerik:RadPane>
</telerik:RadPaneGroup>
</telerik:RadSplitContainer>
</telerik:RadDocking.DocumentHost>
</telerik:RadDocking>
</Grid>
</Window>
Here is my C#:
using System.Windows;
namespace RadDockCloseButton1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public static class RadDockingCommands
{
private static RoutedUICommand closeAllPanesButThisCommand;
public static RoutedUICommand CloseAllPanesButThisCommand
{
get
{
if (closeAllPanesButThisCommand == null)
{
closeAllPanesButThisCommand = new RoutedUICommand("Close all panes but this", "CloseAllPanesButThisCommand", typeof(RadDockingCommands));
}
return closeAllPanesButThisCommand;
}
}
public static void OnCloseAllPanesButThis(object sender, ExecutedRoutedEventArgs e)
{
var pane = e.Parameter as RadPane;
if (pane != null)
{
var paneGroup = pane.PaneGroup;
if (paneGroup != null)
{
var panesToClose = paneGroup.EnumeratePanes().Where(x => !x.IsHidden && x.IsPinned);
foreach (var paneToClose in panesToClose)
{
if (paneToClose != pane)
{
paneToClose.IsHidden = true;
}
}
}
}
}
public static void OnCloseAllPanesButThisCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = false;
var paneGroup = sender as RadPaneGroup;
if (paneGroup != null)
{
int childrenCount = paneGroup.EnumeratePanes().Count(x => !x.IsHidden && x.IsPinned);
if (childrenCount > 1)
{
e.CanExecute = true;
}
else
{
e.CanExecute = false;
}
}
}
}
}
}

I'm adding a second answer with a full and complete code sample. Note that the entirety of this sample was directly taken from the Telerik How to Customize or Remove the RadPane's Menu. I've only put the pieces together from the various snippets. In other words, this is an OOB implementation from the Telerik tutorial.
XAML
<Window x:Class="so.Tel.RadPaneCloseAll.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:so.Tel.RadPaneCloseAll"
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
Title="MainWindow"
Width="525"
Height="350"
WindowStartupLocation="CenterScreen">
<Window.Resources>
<DataTemplate x:Key="ContextMenuTemplate">
<telerik:RadContextMenu InheritDataContext="False">
<telerik:RadMenuItem Command="telerik:RadDockingCommands.Floating"
CommandParameter="{Binding}"
CommandTarget="{Binding}"
Header="{Binding Command.Text,
RelativeSource={RelativeSource Self}}"
IsChecked="{Binding IsFloatingOnly}" />
<telerik:RadMenuItem Command="telerik:RadDockingCommands.Dockable"
CommandParameter="{Binding}"
CommandTarget="{Binding}"
Header="{Binding Command.Text,
RelativeSource={RelativeSource Self}}"
IsChecked="{Binding IsDockableOptionChecked}" />
<telerik:RadMenuItem Command="local:RadDockingCommands.CloseAllPanesButThisCommand"
CommandParameter="{Binding}"
CommandTarget="{Binding}"
Header="{Binding Command.Text,
RelativeSource={RelativeSource Self}}" />
</telerik:RadContextMenu>
</DataTemplate>
<Style TargetType="telerik:RadPane">
<Setter Property="ContextMenuTemplate" Value="{StaticResource ContextMenuTemplate}" />
</Style>
</Window.Resources>
<Grid>
<telerik:RadDocking>
<telerik:RadDocking.DocumentHost>
<telerik:RadSplitContainer>
<telerik:RadPaneGroup>
<telerik:RadPane Header="Pane 1" />
<telerik:RadPane Header="Pane 2" />
<telerik:RadPane Header="Pane 3" />
<telerik:RadPane Header="Pane 4" />
<telerik:RadPane Header="Pane 5" />
</telerik:RadPaneGroup>
</telerik:RadSplitContainer>
</telerik:RadDocking.DocumentHost>
</telerik:RadDocking>
</Grid>
</Window>
Code Behind
using System.Linq;
using System.Windows;
using System.Windows.Input;
using Telerik.Windows.Controls;
namespace so.Tel.RadPaneCloseAll
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
CommandManager.RegisterClassCommandBinding(
typeof( RadPaneGroup ),
new CommandBinding(
RadDockingCommands.CloseAllPanesButThisCommand,
RadDockingCommands.OnCloseAllPanesButThis,
RadDockingCommands.OnCloseAllPanesButThisCanExecute ) );
}
}
public static class RadDockingCommands
{
private static RoutedUICommand closeAllPanesButThisCommand;
public static RoutedUICommand CloseAllPanesButThisCommand
{
get
{
if( closeAllPanesButThisCommand == null )
{
closeAllPanesButThisCommand = new RoutedUICommand( "Close all panes but this",
"CloseAllPanesButThisCommand",
typeof( RadDockingCommands ) );
}
return closeAllPanesButThisCommand;
}
}
public static void OnCloseAllPanesButThis( object sender, ExecutedRoutedEventArgs e )
{
var pane = e.Parameter as RadPane;
if( pane != null )
{
var paneGroup = pane.PaneGroup;
if( paneGroup != null )
{
var panesToClose = paneGroup.EnumeratePanes().Where( x => !x.IsHidden && x.IsPinned );
foreach( var paneToClose in panesToClose )
{
if( paneToClose != pane )
{
paneToClose.IsHidden = true;
}
}
}
}
}
public static void OnCloseAllPanesButThisCanExecute( object sender, CanExecuteRoutedEventArgs e )
{
e.CanExecute = false;
var paneGroup = sender as RadPaneGroup;
if( paneGroup != null )
{
int childrenCount = paneGroup.EnumeratePanes().Count( x => !x.IsHidden && x.IsPinned );
if( childrenCount > 1 )
{
e.CanExecute = true;
}
else
{
e.CanExecute = false;
}
}
}
}
}

Based on your code, you're almost there.
<Window x:Class="RadDockCloseButton1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
xmlns:local="clr-namespace:Telerik.Windows.Controls;assembly=Telerik.Windows.Controls.Docking"
Title="MainWindow" Height="350" Width="525">
Should be (notice the local namespace):
<Window x:Class="RadDockCloseButton1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
xmlns:local="clr-namespace:RadDockCloseButton1"
Title="MainWindow" Height="350" Width="525">
The local namespace is referring to which class has the custom commmand. In this case, you've added the class to your RadDockCloseButton1 namespace.
Register the command in your constructor:
public MainWindow()
{
InitializeComponent();
CommandManager.RegisterClassCommandBinding(
typeof( RadPaneGroup ),
new CommandBinding(
RadDockingCommands.CloseAllPanesButThisCommand,
RadDockingCommands.OnCloseAllPanesButThis,
RadDockingCommands.OnCloseAllPanesButThisCanExecute ) );
}
And move the public static class RadDockingCommands class so that it is not nested inside of the MainWindow. i.e.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
CommandManager.RegisterClassCommandBinding(
typeof( RadPaneGroup ),
new CommandBinding(
RadDockingCommands.CloseAllPanesButThisCommand,
RadDockingCommands.OnCloseAllPanesButThis,
RadDockingCommands.OnCloseAllPanesButThisCanExecute ) );
}
}
public static class RadDockingCommands
{
private static RoutedUICommand closeAllPanesButThisCommand;
// etc...
}
Finally, test with several panes. Either add additional panes to your sample, or just use this:
<telerik:RadDocking>
<telerik:RadDocking.DocumentHost>
<telerik:RadSplitContainer>
<telerik:RadPaneGroup>
<telerik:RadPane Header="Pane 1" />
<telerik:RadPane Header="Pane 2" />
<telerik:RadPane Header="Pane 3" />
<telerik:RadPane Header="Pane 4" />
<telerik:RadPane Header="Pane 5" />
</telerik:RadPaneGroup>
</telerik:RadSplitContainer>
</telerik:RadDocking.DocumentHost>
</telerik:RadDocking>
If this still isn't working for you, I'll post the entire working sample.

Related

WPF DataGrid programmatically go to First Row

This is my code
Version 1
public void scrollIntoGrid(DataGrid grid)
{
object item = grid.Items[0];
grid.SelectedItem = item;
grid.ScrollIntoView(item);
grid.MoveFocus(new TraversalRequest(FocusNavigationDirection.First));
grid.SelectedIndex = 0;
//DataGridRow row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromItem(grid.SelectedItem);
//row.MoveFocus(new TraversalRequest(FocusNavigationDirection.Left));
//row.MoveFocus(new TraversalRequest(FocusNavigationDirection.Up));
}
Version 2
public void scrollIntoGrid(DataGrid grid)
{
object item = grid.Items[0];
grid.SelectedItem = item;
grid.ScrollIntoView(item);
//grid.MoveFocus(new TraversalRequest(FocusNavigationDirection.First));
//grid.SelectedIndex = 0;
DataGridRow row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromItem(grid.SelectedItem);
row.MoveFocus(new TraversalRequest(FocusNavigationDirection.First));
//row.MoveFocus(new TraversalRequest(FocusNavigationDirection.Up));
}
and all iterations go to second row in grid and not the first.
The following code does work for me. On Button-Click it selects the first Row. If that is that what you mean with go to first row.
If the your interpretation of the first row is the header row then this would not work.
Your scroolIntoGrid function could look like this:
public void scrollIntoGrid(DataGrid grid)
{
if(grid != null && grid.HasItems)
{
grid.SelectedIndex = 0;
grid.Focus();
}
}
Full code here:
Code behind file:
public class Person
{
public string Firstname { get; set; }
public string Lastname { get; set; }
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var listOfPersons = new List<Person> { {
new Person { Firstname="Hello",Lastname="World"} },
new Person {Firstname="World",Lastname="Hello"} };
mygrid.DataContext = listOfPersons;
}
private void btnSearch_Click(object sender, RoutedEventArgs e)
{
scrollIntoGrid(this.mygrid);
}
public void scrollIntoGrid(DataGrid grid)
{
if(grid != null && grid.HasItems)
{
grid.SelectedIndex = 0;
grid.Focus();
}
}
}
}
XAML File:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid >
<StackPanel Orientation="Vertical" Grid.Column="2" Margin="20,10,10,10">
<Button Content="Search"
Height="25" HorizontalAlignment="Right" Margin="20,10,10,10"
Name="btnSearch" VerticalAlignment="Top" Width="75" TabIndex="1300" Click="btnSearch_Click" />
</StackPanel>
<DataGrid x:Name="mygrid" AutoGenerateColumns="False" Grid.Column="1" Margin="20,160,10,10"
BorderThickness="2" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Focusable="True"
ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Auto" GridLinesVisibility="None"
ItemsSource="{Binding}" CanUserSortColumns="False" CanUserResizeColumns="False" IsReadOnly="True"
>
<DataGrid.Columns>
<DataGridTextColumn Header="Shortname" Width="100" Binding="{Binding Firstname}" />
<DataGridTextColumn Header="Internal Code" Width="100" Binding="{Binding Lastname}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
Based on your comment that you actually want to jump from a textbox on arrow down key the following changes did the trick for me.
Added a XAML TextBox with an PreviewKeyDown event
<TextBox x:Name="searchBox" PreviewKeyDown="searchBox_KeyDown"></TextBox>
And in code behind:
private void searchBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Down) // The Arrow-Down key
{
scrollIntoGrid(mygrid);
e.Handled = true;
Keyboard.Focus(mygrid);
}
}
ScrollIntoGrid stayed the same as described above.

WPF - UserControl - Binding Dependency Properties

I am doing a user control, to be able to display logs. I would like the user to be able to select lines to copy and paste. I plan to use a RichTextBox, because depending on the type of log line (Warn, Info, Error), I change the color.
Here is the code:
<UserControl x:Class="WpfAppFrameLogging.UserControls.LoggerControl"
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:local="clr-namespace:WpfAppFrameLogging.UserControls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<RichTextBox x:Name="RichText"
IsReadOnly="True"
ScrollViewer.VerticalScrollBarVisibility="Auto">
<FlowDocument x:Name="FlowDoc">
<FlowDocument.Resources>
<Style TargetType="{x:Type Paragraph}">
<Setter Property="Margin" Value="0"/>
</Style>
</FlowDocument.Resources>
</FlowDocument>
</RichTextBox>
</Grid>
</UserControl>
There, I will want with a dependency properties of type ObservableCollection , and when there is an addition of a log line in my controller, that it adds in the RichTextBox a new paragraph.
Something that would look like this.
Codebehind UserControl
var paragraphLog = new Paragraph(new Run(textLog));
paragraphLog.Margin = new Thickness(0, 0, 0, 0);
switch (status)
{
case StatusLog.Error:
paragraphLog.Foreground = Brushes.Red;
break;
case StatusLog.Success:
paragraphLog.Foreground = Brushes.Green;
break;
case StatusLog.Warn:
paragraphLog.Foreground = Brushes.Orange;
break;
case StatusLog.Info: // Useless--> I know
default:
paragraphLog.Foreground = Brushes.Black;
break;
}
FlowDoc.Blocks.Add(paragraphLog);
RichText.ScrollToEnd();
Class LogInfo
public class LogInfo
{
public string TextLog { get; set; }
public StatusLog StatusLog { get; set; }
}
public enum StatusLog
{
Info, // --> Black
Warn, // --> Orange
Error, // --> Red
Success // --> Green
}
And in the views I'll be using the UserControl:
<myControls:LoggerControl AllLogsInfo="{Binding MainLogInfos, Mode=OneWay}" />
With MainLogInfos
public ObservableCollection<LogInfo> MainLogInfos
{
get { return _mainLogInfos; }
set
{
if (value != _mainLogInfos)
{
_mainLogInfos = value;
NotifyPropertyChanged();
}
}
}
private ObservableCollection<LogInfo> _mainLogInfos;
// Method :
public void LogError(string message)
{
MainLogInfos.Add(new LogInfo() { TextLog = message, StatusLog = StatusLog.Error });
}
I am limited to the 4.7.2 framework.
Thank you for your help, ideas, suggestions ...
This example are using a Listbox with a TextBox. It is set to look like a TextBlock. The Style are used to set the color depending on the StatusLog. I have included 4 log messages with different status. You can select multiple lines and I have included a context menu for copying the selected text.
MainWindow.xaml
<Window x:Class="SO65185215.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SO65185215"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ListBox ItemsSource="{Binding MainLogInfos}">
<ListBox.Resources>
<Style x:Key="TextBoxStyle" TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding StatusLog}" Value="Warn">
<Setter Property="Foreground" Value="Orange"/>
</DataTrigger>
<DataTrigger Binding="{Binding StatusLog}" Value="Error">
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding StatusLog}" Value="Success">
<Setter Property="Foreground" Value="Green"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox x:Name="tb" Text="{Binding TextLog}"
BorderThickness="0"
Background="Transparent"
IsReadOnly="True"
Style="{StaticResource TextBoxStyle}">
<TextBox.ContextMenu>
<ContextMenu>
<MenuItem Command="ApplicationCommands.Copy"/>
</ContextMenu>
</TextBox.ContextMenu>
</TextBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Collections.ObjectModel;
using System.Windows;
namespace SO65185215
{
public partial class MainWindow : Window
{
public MainWindow()
{
DataContext = this;
InitializeComponent();
Loaded += MainWindow_Loaded;
}
public ObservableCollection<LogInfo> MainLogInfos { get; } = new ObservableCollection<LogInfo>();
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
MainLogInfos.Add(new LogInfo() { TextLog = "log message 1", StatusLog = StatusLog.Info });
MainLogInfos.Add(new LogInfo() { TextLog = "log message\nNext line message 2", StatusLog = StatusLog.Error });
MainLogInfos.Add(new LogInfo() { TextLog = "log message3\nWarning", StatusLog = StatusLog.Warn });
MainLogInfos.Add(new LogInfo() { TextLog = "log message success", StatusLog = StatusLog.Success });
}
}
public class LogInfo
{
public string TextLog { get; set; }
public StatusLog StatusLog { get; set; }
}
public enum StatusLog
{
Info, // --> Black
Warn, // --> Orange
Error, // --> Red
Success // --> Green
}
}
Edit:
If your purpose was to select several messages, then you can set the ListBox to Multipel/Extended selectionMode:
<ListBox x:Name="listBox" ItemsSource="{Binding MainLogInfos}" SelectionMode="Multiple">
Move the context menu to the Listbox
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Copy selected" Click="CopySelected_Clicked"/>
</ContextMenu>
</ListBox.ContextMenu>
Concatenate the selected texts to the clipboard in the Click event handler.
Observe that the selectedItems collection contains messages in the order the messages are selected.
private void CopySelected_Clicked(object sender, RoutedEventArgs e)
{
StringBuilder sb = new StringBuilder();
foreach(LogInfo item in listBox.SelectedItems)
{
sb.AppendLine(item.TextLog);
}
if (sb.Length > 0)
Clipboard.SetText(sb.ToString());
}
I found a solution
My LoggerControl.xaml
<UserControl x:Class="SDK.FrontOffice.CustomControl.LoggerControl"
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:local="clr-namespace:SDK.FrontOffice.CustomControl"
xmlns:controls="http://metro.mahapps.com/winfx/xaml/controls"
xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks"
xmlns:converter="clr-namespace:SDK.FrontOffice.Converters"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<!--<Grid.Resources>
<converter:TypeInfoToColorConverter x:Key="converterTypeInfo" />
</Grid.Resources>-->
<!--<GroupBox Header="Logs">-->
<RichTextBox x:Name="RichText"
IsReadOnly="True"
ScrollViewer.VerticalScrollBarVisibility="Auto">
<FlowDocument x:Name="FlowDoc">
<FlowDocument.Resources>
<Style TargetType="{x:Type Paragraph}">
<Setter Property="Margin" Value="0"/>
</Style>
</FlowDocument.Resources>
</FlowDocument>
</RichTextBox>
<!--</GroupBox>-->
</Grid>
</UserControl>
And code behind
/// <summary>
/// Logique d'interaction pour LoggerControl.xaml
/// </summary>
public partial class LoggerControl : UserControl
{
public LoggerControl()
{
InitializeComponent();
}
#region DependencyProperty
public ObservableCollection<LogInfo> AllLogs
{
get { return (ObservableCollection<LogInfo>)GetValue(AllLogsProperty); }
set { SetValue(AllLogsProperty, value); }
}
// Using a DependencyProperty as the backing store for AllLogs. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AllLogsProperty =
DependencyProperty.Register("AllLogs",
typeof(ObservableCollection<LogInfo>),
typeof(LoggerControl),
new PropertyMetadata(new ObservableCollection<LogInfo>(),
OnAllLogsChanged));
private static void OnAllLogsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
LoggerControl loggerControl = sender as LoggerControl;
var old = e.OldValue as ObservableCollection<LogInfo>;
if (old != null)
{
old.CollectionChanged -= loggerControl.OnWorkCollectionChanged;
}
var nouvelleValeur = e.NewValue as ObservableCollection<LogInfo>;
if (nouvelleValeur != null)
{
nouvelleValeur.CollectionChanged += loggerControl.OnWorkCollectionChanged;
}
// Reset de la liste.
if(old.Count > 0 && nouvelleValeur.Count == 0)
loggerControl.FlowDoc.Blocks.Clear();
}
}
/// <summary>
/// Méthode quand la collection est modifiée.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnWorkCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Reset)
{
// Clear and update entire collection
this.FlowDoc.Blocks.Clear();
}
if (e.Action == NotifyCollectionChangedAction.Add)
{
var nouvelleCollection = sender as ObservableCollection<LogInfo>;
var derniereLigne = nouvelleCollection.Last();
Paragraph paragraphLog = new Paragraph(new Run(derniereLigne.InformationMessage));
paragraphLog.Margin = new Thickness(0, 0, 0, 0);
witch (derniereLigne.StatusLog)
case StatusLog.Error:
paragraphLog.Foreground = Brushes.Red;
break;
case StatusLog.Warn:
paragraphLog.Foreground = Brushes.Orange;
break;
case StatusLog.InfoGreen:
paragraphLog.Foreground = Brushes.Green;
break;
case StatusLog.Information:
default:
paragraphLog.Foreground = Brushes.Black;
break;
this.FlowDoc.Blocks.Add(paragraphLog);
this.RichText.ScrollToEnd();
}
}
}

WPF + Caliburn Micro + MVVM: TabItem handling

I am trying to make a popup window which contains tabcontrol using WPF, Caliburn Micro and MVVM pattern, no need to use code behind in this case. The tabcontrol contains more than 1 tabitem. After digging some threads in SO for a while I combine the found solutions and can create the popup window and fill it with tabcontrol and its tabitems (I take it from this thread).
Problem: the tabitems show content (text) from view model but show no content from view. Please take a look the code attached here.
Expected I expect to see the text "Tab Item 1" as TabItem1 header and the text "Selection One" as content in TabItem1. Right now both the header and the content of TabItems contains same text "Tab Item 1".
Am I missing something? I attach here the code. Please feel free change the code. Any hints are highly appreciated.
Sequence of code:
TabItem1, TabItem2 view and viewmodel
ITabItem
PopUp window view and viewmodel
AppBootstrapper, Shell view and viewmodel
TabItem1ViewModel (TabItem2ViewModel has same content)
public class TabItem1ViewModel : Screen, ITabItem
{
public TabItem1ViewModel() => DisplayName = "Tab Item 1";
}
Attention: in following TabItem view I use Label to show the text "Selection One", but this text doesn't appear at all. Only the display name "Tab Item 1" defined in view model appears as content of TabItem1
TabItem1View (TabItem2View has same content)
<UserControl
x:Class="CmTabControl.Views.TabItem1View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<Grid>
<TabItem x:Name="TabItem1" Header="{Binding Path=DisplayName}">
<Grid x:Name="TabItem1ContentGrid">
<Label HorizontalAlignment="Left"
VerticalAlignment="Top"
Content="Selection One" />
</Grid>
</TabItem>
</Grid>
</UserControl>
ITabItem (yes, it is only empty interface)
public interface ITabItem : IScreen
{
}
PopUpViewModel
public class PopUpViewModel : Screen
{
public PopUpViewModel()
{
TabItems.Add(new TabItem1ViewModel());
TabItems.Add(new TabItem2ViewModel());
}
private readonly BindableCollection<ITabItem> _tabItems = new BindableCollection<ITabItem>();
public BindableCollection<ITabItem> TabItems
{
get => _tabItems;
set
{
if (_tabItems == null)
{
return;
}
_tabItems.Clear();
_tabItems.AddRange(value);
NotifyOfPropertyChange(() => TabItems);
}
}
}
PopUpView
<Window
x:Class="CmTabControl.Views.PopUpView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:CmTabControl.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="PopUpView"
Width="800"
Height="450"
mc:Ignorable="d">
<Grid Margin="3,8,3,3" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TabControl x:Name="TabItems" />
</Grid>
</Window>
AppBootstrapper
public class AppBootstrapper : BootstrapperBase
{
private readonly SimpleContainer _container = new SimpleContainer();
public AppBootstrapper() => Initialize();
protected override object GetInstance(Type serviceType, string key) => _container.GetInstance(serviceType, key);
protected override IEnumerable<object> GetAllInstances(Type serviceType) => _container.GetAllInstances(serviceType);
protected override void BuildUp(object instance) => _container.BuildUp(instance);
protected override void Configure()
{
base.Configure();
_container.Singleton<IWindowManager, WindowManager>();
_container.Singleton<IEventAggregator, EventAggregator>();
_container.Singleton<ShellViewModel>();
_container.PerRequest<PopUpViewModel>(); // Or Singleton if there'll only ever be one
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
base.OnStartup(sender, e);
DisplayRootViewFor<ShellViewModel>();
}
}
ShellViewModel
public class ShellViewModel : Conductor<object>.Collection.AllActive
{
private IWindowManager _windowManager;
public ShellViewModel(PopUpViewModel popUpVm)
{
DisplayName = "Shell Window";
PopUpViewModel = popUpVm;
}
public PopUpViewModel PopUpViewModel { get; set; }
public sealed override void ActivateItem(object item) => base.ActivateItem(item);
public void OpenPopUp()
{
ActivateItem(PopUpViewModel);
if (_windowManager == null) _windowManager = new WindowManager();
_windowManager.ShowDialog(PopUpViewModel, null, null);
}
public sealed override string DisplayName { get; set; }
}
ShellView
<UserControl
x:Class="CmTabControl.Views.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<Grid Width="300" Height="300"
HorizontalAlignment="Center" VerticalAlignment="Center">
<Button x:Name="OpenPopUp" Width="100" Height="35"
Content="Open Popup" />
</Grid>
</UserControl>
Added: Screenshot of Live Visual Tree.
I found a solution that uses Templates:
PopUpViewModel add SelectedTab:
public sealed class PopUpViewModel : Screen
{
private readonly BindableCollection<ITabItem> _tabItems = new BindableCollection<ITabItem>();
private IScreen _selectedTab;
public PopUpViewModel()
{
DisplayName = "Popup";
TabItems.Add(new TabItem1ViewModel());
TabItems.Add(new TabItem2ViewModel());
SelectedTab = TabItems.FirstOrDefault();
}
public BindableCollection<ITabItem> TabItems
{
get => _tabItems;
set
{
if(_tabItems == null)
return;
_tabItems.Clear();
_tabItems.AddRange(value);
NotifyOfPropertyChange(() => TabItems);
}
}
public IScreen SelectedTab
{
get => _selectedTab;
set
{
_selectedTab = value;
NotifyOfPropertyChange();
}
}
}
PopUpView:
<Grid Margin="3, 8, 3, 3"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TabControl ItemsSource="{Binding TabItems}"
SelectedItem="{Binding SelectedTab,
UpdateSourceTrigger=PropertyChanged}">
<TabControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding DisplayName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl cal:View.Model="{Binding}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
Now you only add the TabItem content to your pages, TabItem1View:
<UserControl x:Class="WpfTestApp.Views.Tabs.TabItem1View"
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:local="clr-namespace:WpfTestApp.Views.Tabs"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800">
<Grid x:Name="TabItem1ContentGrid">
<Label HorizontalAlignment="Left"
VerticalAlignment="Top"
Content="Selection One" />
</Grid>
</UserControl>
Edit:
SelectedTab is just there so the first tab is selected by default.

Proper databinding in WPF using TabControl and MVVM

Just when I thought I was getting better at this, TabControl is now giving me problems. I have read relevant posts here on StackOverflow, but have been unable to get my simple demo application to work the way I want it to.
To keep things focused, I'll start with a single question about something I don't understand.
I have a TabControl whose TabItems each host the same UserControl. When I set the TabControl.ContentTemplate's DataTemplate to my UserControl, a rendering of that control appears, but it looks like it's the same control for each tab. Or perhaps it's not tied to any of the tabs at all.
MainWindow.xaml
<Window x:Class="TabControlMvvm.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:localviews="clr-namespace:TabControlMvvm.Views"
Title="MainWindow" Height="350" Width="525">
<TabControl ItemsSource="{Binding Tabs}" SelectedIndex="{Binding Selected}">
<TabControl.ContentTemplate>
<DataTemplate>
<localviews:PersonMainPanel />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Window>
Code-behind just sets the ViewModel as its DataContext:
namespace TabControlMvvm {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
public MainWindow()
{
InitializeComponent();
DataContext = new TabControlMvvm.ViewModels.MainViewModel();
}
}
}
The TabItem's Content should be another UserControl, PersonMainPanel.xaml:
<UserControl x:Class="TabControlMvvm.Views.PersonMainPanel"
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:localviews="clr-namespace:TabControlMvvm.Views"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Border BorderBrush="Red" BorderThickness="2">
<TabControl TabStripPlacement="Bottom">
<TabItem Header="Tab 1">
<localviews:MyTabItem />
</TabItem>
<TabItem Header="Tab 2">
<TextBlock Text="This was left blank intentionally" />
</TabItem>
<TabItem Header="Tab 3">
<TextBlock Text="This was also left blank intentionally" />
</TabItem>
</TabControl>
</Border>
</UserControl>
Code-behind:
namespace TabControlMvvm.Views {
/// <summary>
/// Interaction logic for PersonMainPanel.xaml
/// </summary>
public partial class PersonMainPanel : UserControl {
public PersonMainPanel()
{
InitializeComponent();
}
}
}
And the MainViewModel:
namespace TabControlMvvm.ViewModels {
public class MainViewModel : ViewModelBase {
public ICollectionView Tabs { get; set; }
public int Selected { get; set; }
public class Person
{
public string Name { get; set; }
}
public class DummyController {
public List<Person> Persons { get; private set; }
public DummyController()
{
Persons = new List<Person> {
new Person { Name = "Larry" },
new Person { Name = "Darryl" },
new Person { Name = "Other brother Darryl" }
};
}
}
public DummyController Controller { get; private set; }
public RelayCommand HelloCommand { get; set; }
public MainViewModel()
{
Controller = new DummyController();
/*
IEnumerable<TabItem> tabs = Enumerable.Range( 1, _controller.Persons.Count())
.Select( x => new TabItem { Header = String.Format( "Person {0}", x),
Content = new PersonMainPanel() });
*/
IEnumerable<TabItem> tabs = Enumerable.Range( 1, Controller.Persons.Count())
.Select( x => new TabItem { Header = String.Format( "Person {0}", x)});
Tabs = CollectionViewSource.GetDefaultView( tabs.ToList());
Tabs.MoveCurrentToFirst();
InitializeCommands();
}
private void InitializeCommands()
{
HelloCommand = new RelayCommand( () => { MessageBox.Show( String.Format( "Hello, Person {0} named {1}!",
Selected, Controller.Persons[Selected].Name)); });
}
}
}
PersonMainPanel hosts another TabControl, where Tab 1's Content is MyTabItem.xaml:
<UserControl x:Class="TabControlMvvm.Views.MyTabItem"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name:" />
<TextBox Text="{Binding Name}" Width="100" />
</StackPanel>
<Button Command="{Binding HelloCommand}" Content="Say Hello" />
</StackPanel>
</UserControl>
Code-behind:
namespace TabControlMvvm.Views {
/// <summary>
/// Interaction logic for MyTabItem.xaml
/// </summary>
public partial class MyTabItem : UserControl {
public MyTabItem()
{
InitializeComponent();
}
}
}
Which looks like this at runtime:
Issues I have so far:
When I enter Person 1's Name and then click the Person 2 tab, Person 1's Name is still visible, hence my assumption that the controls are not databound properly. I understand that ItemsControls do not pass their DataContext down to their children, but I am not sure how to fix this without associating the View in code-behind.
I would have expected to get databinding errors in the Output window because of the missing DataContext, but I don't get any errors. I assume the DataContext is null, but wouldn't this still result in a binding error?
How can I use Snoop effectively to debug problems like this?
Here's the sample solution: http://www.filedropper.com/tabcontrolmvvm
Here is solution:
In MainWindow modify your TabControl template, to bind Header from your Model:
<TabControl ItemsSource="{Binding Tabs}" SelectedIndex="{Binding Selected}">
<TabControl.ContentTemplate>
<DataTemplate>
<localviews:PersonMainPanel />
</DataTemplate>
</TabControl.ContentTemplate>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Header}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
In MyTabItem.xaml, set UpdateTrigger, because default one 'OnLostFocus' can sometimes not save your data:
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" Width="100" />
In MainViewModel modify creating of your tabs, so it will have Name property too:
IEnumerable<TabItem> tabs = Enumerable.Range( 1, Controller.Persons.Count())
.Select( x => new TabItem { Header = String.Format("Person {0}", x), Name = Controller.Persons[x-1].Name });
Also, the most important, create own TabItem class to contain some bounded data:
public class TabItem
{
public string Name { set; get; }
public string Header { set; get; }
}

WPF Custom control - Binding to command defined in code-behind

I'm trying to create a WPF custom control called "DataTextBox". Everything works fine except for the context menu of this control. Indeed, I would like to add an item in the DataTextBox's Context Menu. To do this, I have added a MenuItem in my DataTextBox style defined in generic.xaml:
<Style TargetType="{x:Type controls:DataTextBox}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="{DynamicResource Components_TextBoxCut}" Command="ApplicationCommands.Cut" />
<MenuItem Header="{DynamicResource Components_TextBoxCopy}" Command="ApplicationCommands.Copy" />
<MenuItem Header="{DynamicResource Components_TextBoxPaste}" Command="ApplicationCommands.Paste" />
<MenuItem x:Name="menuItemInsertChecksum" Header="{DynamicResource Components_DataTextBoxInsertChecksum}"
Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type controls:DataTextBox}}, Path=CalculateChecksumCommand}" />
</ContextMenu>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
...
</Setter.Value>
</Setter>
</Style>
I have also added a command in the DataTextBox code-behind
public static DependencyProperty CalculateChecksumCommandProperty = DependencyProperty.Register("CalculateChecksumCommand", typeof(ICommand), typeof(DataTextBox));
public ICommand CalculateChecksumCommand { get; private set; }
This command is initialized in the DataTextBox constructor :
public DataTextBox() : base()
{
CalculateChecksumCommand = new RelayCommand(() => CalculateChecksum(), () => CanCalculateChecksum());
}
The issue I have is that the Command binding of my last MenuItem does not work because the "CalculateChecksumCommand" is not found. This means that the "CalculateChecksum()" method is never called.
I would appreciate any help on that subject. Thank you.
EDIT : The Dependency Property declaration should be :
public static DependencyProperty CalculateChecksumCommandProperty = DependencyProperty.Register("CalculateChecksumCommand", typeof(ICommand), typeof(DataTextBox));
public ICommand CalculateChecksumCommand
{
get { return (ICommand)GetValue(CalculateChecksumCommandProperty); }
private set { SetValue(CalculateChecksumCommandProperty, value); }
}
The window that hosts a control and defines a style for it which binds one menu item of its context menu to a command of it :
XAML
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:wpfApplication2="clr-namespace:WpfApplication2"
Title="MainWindow"
Width="525"
Height="350"
mc:Ignorable="d">
<Grid>
<Grid.Resources>
<Style TargetType="wpfApplication2:UserControl1" x:Shared="False">
<Style.Setters>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ContextMenu}, Path=PlacementTarget.(wpfApplication2:UserControl1.MyCommand)}" Header="Hello" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
</Grid.Resources>
<wpfApplication2:UserControl1 />
</Grid>
</Window>
Code
using System;
using System.Windows;
using System.Windows.Input;
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class DelegateCommand : ICommand
{
private readonly Func<object, bool> _canExecute;
private readonly Action<object> _execute;
public DelegateCommand(Action<object> execute) : this(execute, s => true)
{
}
public DelegateCommand(Action<object> execute, Func<object, bool> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
public event EventHandler CanExecuteChanged;
}
}
Control :
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl, INotifyPropertyChanged
{
private DelegateCommand _myCommand;
public UserControl1()
{
InitializeComponent();
MyCommand = new DelegateCommand(Execute);
}
public DelegateCommand MyCommand
{
get { return _myCommand; }
set
{
_myCommand = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void Execute(object o)
{
MessageBox.Show("Hello");
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
With comments from Aybe and nkoniishvt, I think that I'm able to answer my own question.
Objective: create a command in a CustomControl (not UserControl) and use it in the xaml part of this CustomControl
(As nkoniishvt said, commands are typically used in ViewModel and not in UI components. However, I have not found any similar solutions.)
CustomControl code-behind:
using GalaSoft.MvvmLight.Command;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace WpfApplication1
{
public class CustomControl1 : TextBox
{
public CustomControl1()
: base()
{
MyCommand = new RelayCommand(() => Execute(), () => CanExecute());
}
static CustomControl1()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
}
public static DependencyProperty MyCommandProperty = DependencyProperty.Register("MyCommand", typeof(ICommand), typeof(CustomControl1));
public ICommand MyCommand
{
get { return (ICommand)GetValue(MyCommandProperty); }
private set { SetValue(MyCommandProperty, value); }
}
private void Execute()
{
//Do stuff
}
private bool CanExecute()
{
return true;
}
}
}
CustomControl appearance defined in Themes/Generic.xaml:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1">
<Style TargetType="{x:Type local:CustomControl1}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Cut" Command="ApplicationCommands.Cut" />
<MenuItem Header="Copy" Command="ApplicationCommands.Copy" />
<MenuItem Header="Past" Command="ApplicationCommands.Paste" />
<MenuItem Header="Execute MyCommand in CustomControl1"
Command="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.TemplatedParent.MyCommand}" />
<!--In this case, PlacementTarget is "txtBox"
This is why we have to find the templated parent of the PlacementTarget because MyCommand is defined in the CustomControl1 code-behind-->
</ContextMenu>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
<Grid>
<!--Some UI elements-->
<TextBox Name="txtBox" ContextMenu="{TemplateBinding ContextMenu}" />
<!--Others UI elements-->
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Example of use of this CustomControl in MainWindow.xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:CustomControl1 />
</Grid>
</Window>
Do not forget to add resources in App.xaml:
<Application x:Class="WpfApplication1.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="MainWindow.xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" d1p1:Ignorable="d" xmlns:d1p1="http://schemas.openxmlformats.org/markup-compatibility/2006">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Themes/Generic.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
By running the application, we can see that MyCommand is properly bound. This means that the Execute() method is called when the user click on the fourth MenuItem in the ContextMenu.
If you see areas for improvement, thank you to let me know.
Hoping it will help someone.
Have you tried to implement a CustomRoutedCommand?
This works for my CustomControl:
public static RoutedCommand CustomCommand = new RoutedCommand();
CommandBinding CustomCommandBinding = new CommandBinding(CustomCommand, ExecutedCustomCommand, CanExecuteCustomCommand);
this.CommandBindings.Add(CustomCommandBinding);
customControl.Command = CustomCommand;
KeyGesture kg = new KeyGesture(Key.F, ModifierKeys.Control);
InputBinding ib = new InputBinding(CustomCommand, kg);
this.InputBindings.Add(ib);
private void ExecutedCustomCommand(object sender, ExecutedRoutedEventArgs e)
{
//what to do;
MessageBox.Show("Custom Command Executed");
}
private void CanExecuteCustomCommand(object sender, CanExecuteRoutedEventArgs e)
{
Control target = e.Source as Control;
if (target != null)
{
e.CanExecute = true;
}
else
{
e.CanExecute = false;
}
}
another interesting example

Categories