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();
}
}
}
Related
So I have this checkbox in WPF.
<CheckBox
Name="folder_browser" Checked="{}" Unchecked="{}"
Content="Folder browser" Foreground="Black"
Grid.Row="5" Grid.Column="0"
Visibility="{Binding Visibility}">
</CheckBox>
I have to bind its checked and unchecked in such a way that with check, a certain button is visible and when unchecked, a different button. Both are separate buttons. The location of both is same on UI so I am getting confused.
Use a single Button and set its Content by a DataTrigger in a Button Style:
<Button Click="BrowseButtonClicked">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Content" Value="File..."/>
<Style.Triggers>
<DataTrigger
Binding="{Binding IsChecked, ElementName=folder_browser}"
Value="True">
<Setter Property="Content" Value="Folder..."/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
In the Click handler, perform different actions according to the check state of the CheckBox:
private void BrowseButtonClicked(object sender, RoutedEventArgs e)
{
if (folder_browser.IsChecked == true)
{
//...
}
else
{
//...
}
}
whenever your view elements cannot have their property directly bind to source, then you need to implement a viewmodel as intermediate translator.
I made a simple demo WPF application for you. To repeate what I have done, create a new WPF application project named "CheckboxAndButtonVisibilityDemo", in MainWindow.xaml, copy following code:
<Window x:Name="window" x:Class="CheckboxAndButtonVisibilityDemo.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:CheckboxAndButtonVisibilityDemo"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<CheckBox IsChecked="{Binding ViewModel.CheckBoxChecked, ElementName=window, Mode=TwoWay}"></CheckBox>
<Button Visibility="{Binding ViewModel.ButtonAVisibility, ElementName=window, Mode=OneWay}">ButtonA</Button>
<Button Visibility="{Binding ViewModel.ButtonBVisibility, ElementName=window, Mode=OneWay}">ButtonB</Button>
</StackPanel>
</Window>
Copy following code into MainWindow.xaml.cs
using System.Windows;
namespace CheckboxAndButtonVisibilityDemo {
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
public ViewModel ViewModel { get; } = new ViewModel();
}
}
Add a new file, ViewModel.cs, and copy following code into it:
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
namespace CheckboxAndButtonVisibilityDemo {
public class ViewModel : INotifyPropertyChanged {
private bool checkBoxChecked;
public bool CheckBoxChecked {
get { return checkBoxChecked; }
set {
checkBoxChecked = value;
if (checkBoxChecked) {
ButtonAVisibility = Visibility.Hidden;
ButtonBVisibility = Visibility.Visible;
} else {
ButtonAVisibility = Visibility.Visible;
ButtonBVisibility = Visibility.Hidden;
}
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged([CallerMemberName] String propertyName = null) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private Visibility buttonAVisibility = Visibility.Visible;
public Visibility ButtonAVisibility {
get { return buttonAVisibility; }
private set {
buttonAVisibility = value;
NotifyPropertyChanged();
}
}
private Visibility buttonBVisibility = Visibility.Hidden;
public Visibility ButtonBVisibility {
get { return buttonBVisibility; }
private set {
buttonBVisibility = value;
NotifyPropertyChanged();
}
}
}
}
Now its done, run and test it.
I'm trying to display a treeview in my application but somehow I can't get the binding to work and application displays a blank treeview. Here's the source code.
MainWindow.xaml
<Window x:Class="TreeViewTest.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:TreeViewTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<ListBox x:Name="listbox" MinWidth="200" MinHeight="300">
<TreeView x:Name="treeView" ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=Regions}"
MinWidth="200" MinHeight="300">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Region}" ItemsSource="{Binding Customers}" >
<CheckBox Content="{Binding Name}" IsChecked="{Binding Path=(local:CustomerDataHelper.IsChecked), Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
<CheckBox.Style>
<Style TargetType="{x:Type CheckBox}">
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Visibility" Value="Visible"/>
</Style>
</CheckBox.Style>
</CheckBox>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:Customer}" >
<CheckBox Content="{Binding Name}" IsChecked="{Binding Path=(local:CustomerDataHelper.IsChecked), Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
<CheckBox.Style>
<Style TargetType="{x:Type CheckBox}">
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Visibility" Value="Visible"/>
</Style>
</CheckBox.Style>
</CheckBox>
</DataTemplate>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</ListBox>
<Button Content="?" Click="Button_Click" />
<TextBlock x:Name="textBoxCrew"/>
</StackPanel>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public ObservableCollection<Region> Regions { get; }
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
string crew = "";
foreach (Region Region in this.Regions)
foreach (Customer Customer in Region.Customers)
if (CustomerDataHelper.GetIsChecked(Customer) == true)
crew += Customer.Name + ", ";
crew = crew.TrimEnd(new char[] { ',', ' ' });
this.textBoxCrew.Text = "Selected customers: " + crew;
}
}
MainWindowViewModel.cs
class MainWindowViewModel
{
public MainWindowViewModel()
{
Regions = new ObservableCollection<Region>();
Regions.Add(new Region() { Name = "North", Customers = new List<Customer>() { new Customer() { Name = "N1" }, new Customer() { Name = "N2" } } });
Regions.Add(new Region() { Name = "East", Customers = new List<Customer>() { new Customer() { Name = "E1" }, new Customer() { Name = "E2" } } });
Regions.Add(new Region() { Name = "South", Customers = new List<Customer>() { new Customer() { Name = "S1" } } });
foreach (Region Region in this.Regions)
foreach (Customer Customer in Region.Customers)
Customer.SetValue(CustomerDataHelper.ParentProperty, Region);
}
public ObservableCollection<Region> Regions { get; }
}
Customers.cs
public class Region : DependencyObject
{
public string Name { get; set; }
public List<Customer> Customers { get; set; }
}
public class Customer : DependencyObject
{
public string Name { get; set; }
}
CustomerDataHelper.cs
public class CustomerDataHelper : DependencyObject
{
public static readonly DependencyProperty IsCheckedProperty = DependencyProperty.RegisterAttached("IsChecked", typeof(bool?), typeof(CustomerDataHelper), new PropertyMetadata(false, new PropertyChangedCallback(OnIsCheckedPropertyChanged)));
public static void SetIsChecked(DependencyObject element, bool? IsChecked)
{
element.SetValue(CustomerDataHelper.IsCheckedProperty, IsChecked);
}
public static bool? GetIsChecked(DependencyObject element)
{
return (bool?)element.GetValue(CustomerDataHelper.IsCheckedProperty);
}
public static readonly DependencyProperty ParentProperty = DependencyProperty.RegisterAttached("Parent", typeof(object), typeof(CustomerDataHelper));
public static void SetParent(DependencyObject element, object Parent)
{
element.SetValue(CustomerDataHelper.ParentProperty, Parent);
}
public static object GetParent(DependencyObject element)
{
return (object)element.GetValue(CustomerDataHelper.ParentProperty);
}
private static void OnIsCheckedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is Region && ((bool?)e.NewValue).HasValue)
foreach (Customer p in (d as Region).Customers)
CustomerDataHelper.SetIsChecked(p, (bool?)e.NewValue);
if (d is Customer)
{
int checkedCount = ((d as Customer).GetValue(CustomerDataHelper.ParentProperty) as Region).Customers.Where(x => CustomerDataHelper.GetIsChecked(x) == true).Count();
int uncheckedCount = ((d as Customer).GetValue(CustomerDataHelper.ParentProperty) as Region).Customers.Where(x => CustomerDataHelper.GetIsChecked(x) == false).Count();
if (uncheckedCount > 0 && checkedCount > 0)
{
CustomerDataHelper.SetIsChecked((d as Customer).GetValue(CustomerDataHelper.ParentProperty) as DependencyObject, null);
return;
}
if (checkedCount > 0)
{
CustomerDataHelper.SetIsChecked((d as Customer).GetValue(CustomerDataHelper.ParentProperty) as DependencyObject, true);
return;
}
CustomerDataHelper.SetIsChecked((d as Customer).GetValue(CustomerDataHelper.ParentProperty) as DependencyObject, false);
}
}
}
If I add the view model ctor code in the code behind ctor and run, it works and I can see the tree view items displayed. Can't seem to figure out what I'm missing.
I have a window which contains a TabControl and I would like to have TabItems generated based on user actions. Inside the TabItems I would like to display a UserControl which uses a ViewModel.
I can get everything to display properly, however when the UserControl's ViewModel is updated, the changes are not reflected in the TabItem.
Consider the following simplified example:
MainWindowViewModel.cs
public class MainWindowViewModel : ViewModel
{
public MainWindowViewModel()
{
MyControls = new ObservableCollection<MyControlViewModel>();
}
private ObservableCollection<MyControlViewModel> _myControls;
public ObservableCollection<MyControlViewModel> MyControls
{
get { return _myControls; }
set
{
_myControls = value;
RaisePropertyChangedEvent(nameof(MyControls));
}
}
}
MainWindow.xaml.cs
public partial class MainWindow : Window
{
private static MainWindowViewModel _vm;
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
_vm = DataContext as MainWindowViewModel;
var myItem1 = new MyItem("Item1");
var myItem2 = new MyItem("Item2");
var myControlVm = new MyControlViewModel(new ObservableCollection<MyItem> { myItem1, myItem2 });
_vm.MyControls.Add(myControlVm);
}
}
MainWindow.xaml
<Window x:Class="TabControlTest.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:TabControlTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<TabControl ItemsSource="{Binding MyControls}">
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type local:MyControlViewModel}">
<local:MyControl />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
MyControlViewModel.cs
public class MyControlViewModel : ViewModel
{
public MyControlViewModel(ObservableCollection<MyItem> items)
{
MyItems = items;
}
private ObservableCollection<MyItem> _myItems;
public ObservableCollection<MyItem> MyItems
{
get { return _myItems; }
set
{
_myItems = value;
RaisePropertyChangedEvent(nameof(MyItems));
}
}
}
MyControl.xaml.cs
public partial class MyControl : UserControl
{
public MyControl()
{
InitializeComponent();
}
private void ListBox_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var vm = DataContext as MyControlViewModel;
foreach (var item in vm.MyItems)
{
item.ShouldBold = !item.ShouldBold;
}
}
}
MyControl.xaml
<UserControl x:Class="TabControlTest.MyControl"
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:TabControlTest"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<ListBox ItemsSource="{Binding MyItems}" MouseDoubleClick="ListBox_MouseDoubleClick">
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Content" Value="{Binding Label}" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<DataTrigger Binding="{Binding ShouldBold}" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
</ListBox>
</Grid>
</UserControl>
MyItem.cs
public class MyItem : ViewModel
{
public MyItem(string label)
{
Label = label;
}
private string _label;
public string Label
{
get { return _label; }
set
{
_label = value;
RaisePropertyChangedEvent(nameof(Label));
}
}
private bool _shouldBold;
public bool ShouldBold
{
get { return _shouldBold; }
set
{
_shouldBold = value;
RaisePropertyChangedEvent(nameof(ShouldBold));
}
}
}
When I double-click on the MyControl in the MainWindow I would expect the items to be displayed in bold via the ListBox_MouseDoubleClick method, however the style is not applying. When I step through the ListBox_MouseDoubleClick method, I can see that the ShouldBold is being updated correctly, but again the style is not applying.
Is there another way I should structure this or something else I need to do to have the style apply? Any help is appreciated!
Edit
Here's my ViewModel.cs
public class ViewModel
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChangedEvent(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Your ViewModel class should implement INotifyPropertyChanged:
public class ViewModel : INotifyPropertyChanged
...
Hi I have use the following Code snippet to create custom control for generating multiple Menuitems and it is the code snippet of example to explain my working scenorio
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:WpfCustomControlLibrary1">
<Style TargetType="{x:Type local:CustomControl1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
<Menu ItemsSource="{Binding ItemsSource,RelativeSource={RelativeSource TemplatedParent}}">
<Menu.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</Menu.ItemTemplate>
</Menu>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
The following code snippet i have created the DependencyProperty in CustomControl
public class CustomControl1 : Control
{
static CustomControl1()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
}
public object ItemsSource
{
get { return (object)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
// Using a DependencyProperty as the backing store for ItemsSource. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(object), typeof(CustomControl1), new PropertyMetadata(0));
}
And also i have intizialize this custom control in my xaml part like below code snippet
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:CustomLib="clr-namespace:WpfCustomControlLibrary1;assembly=WpfCustomControlLibrary1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<Button Content="Add" Click="Button_Click"></Button>
<CustomLib:CustomControl1 Name="CustomControl" ItemsSource="{Binding ListItems}" Grid.Row="1"/>
</Grid>
</Window>
I have created the ListItems in MainWindows.xaml.cs file and also define the DataContext as this.DataContex as this like below code snippet
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window,INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
CreateListItems();
}
private void CreateListItems()
{
this.ListItems = new List<MenuModel>();
this.ListItems.Add(new MenuModel() { Header = "Test1" });
this.ListItems.Add(new MenuModel() { Header = "Test2" });
this.ListItems.Add(new MenuModel() { Header = "Test3" });
}
private List<MenuModel> listItems;
public List<MenuModel> ListItems
{
get
{
return listItems;
}
set
{
listItems = value;
RaisePropertyChanged("ListItems");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.ListItems.Add(new MenuModel() { Header = "Test4" });
this.CustomControl.ItemsSource = this.ListItems;
}
}
public class MenuModel
{
public string Header
{
get;
set;
}
}
}
While add button click i have change the ItemsSource but it is not updated. Can you please help me how to achieve this
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.