Bind the tab header to a property from code - wpf

I am binding a collection to a TabControl using its ItemSource property.
I'm programming WPF in code and not in XAML to get a deeper understanding.
The problem I'm faced with is that if I want to bind the header of a TabItem to a property ("EntityID") the binding does not kick in.
The code works if I set a value instead of a binding (code below in comments)
var binding = new Binding();
binding.Path = new PropertyPath("EntityID");
DataTemplate itemTemplate = new DataTemplate();
var label = new FrameworkElementFactory(typeof(Label));
//label.SetValue(Label.ContentProperty,"test");
label.SetBinding(Label.ContentProperty, binding);
itemTemplate.VisualTree = label;
_tabControl.ItemTemplate = itemTemplate;
Furthermore, if set the ContentTemplate instead of the ItemTemplate the binding works as well.
How can I bind the tab header to a property of my ItemsSource from purely code?

There are many ways to set bindings from Code Behind. What you should try to bind is the HeaderProperty on the TabItem. Though you must first retrieve it to do that.
Here is a working example that should set you started. It's not the way I would do it, as I would do that in xaml, but as you requested to do that from code behind, here you go :)
On a side note, it's almost always a bad idea to define templates in Code behind, try to avoid it as much as possible.
Windows.xaml
<Window x:Class="StackOverflow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:StackOverflow"
Title="Super long title of the super window" Width="500" Height="300">
<Window.Resources>
<DataTemplate DataType="{x:Type local:Entity}">
<TextBlock Text="{Binding Id}" FontSize="{Binding Size}" />
</DataTemplate>
</Window.Resources>
<local:MyTabControl x:Name="tabControl" ItemsSource="{Binding Entities}" />
</Window>
Window.xaml.cs
using System.Windows;
using System;
using System.Windows.Data;
using System.Collections.Generic;
using System.Windows.Controls;
namespace StackOverflow
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public IEnumerable<Entity> Entities
{
get
{
for (int i = 0; i < 5; i++)
{
yield return new Entity() { Id = i };
}
}
}
}
public class MyTabControl : TabControl
{
protected override DependencyObject GetContainerForItemOverride()
{
var tabitem = base.GetContainerForItemOverride() as TabItem;
if (tabitem != null)
{
tabitem.Loaded += OnTabItemLoaded;
}
return tabitem;
}
void OnTabItemLoaded(object sender, RoutedEventArgs e)
{
var tabItem = sender as TabItem;
if (tabItem == null)
throw new InvalidOperationException();
tabItem.SetBinding(TabItem.HeaderProperty, new Binding("DisplayName"));
}
}
public class Entity : DependencyObject
{
public int Id { get; set; }
public string DisplayName { get { return "Entity ID : " + Id; } }
}
}

Couple of things...
As a WPF designer and developer, XAML is the best way of GUI and Code
Behind segregation. It does not decrease my understanding of WPF in
any way. So I recommend XAML.
Believe me being a Winforms / ASP.NET developer myself, I was initially reluctant of using XAML, but the titanic amount of code that I had to write and the relationships between various GUI elements and upon that taming the beasts called Templates \ Styles \ Triggers and ResourceDictionaries just using C# code behind was just a plain torture to me.
Enough of my experience, this is about your issue!!
To answer your question, have you set _tabControl.ItemsSource? And make sure that each item from that ItemsSource has EntityID property in it. Your code should work.
If this still doesnt work then try to see in your Visual Studio's Output window, if there are any binding errors.

Related

WPF vs. Silverlight - DataBinding in resources

after some time of silverlight-development I am currently doing some WPF work...
I often used this trick to make my life easier in some of my ValueConverters:
public class MyCovnerterWithDataContext : FrameworkElement, IValueConverter
{
private MyDataContextType Data
{
get { return this.DataContext as MyDataContextType; }
}
....
Now I could access my DataContext in the Converter-Method, which comes handy in lots of situations as you can imagine.
I tried the same trick in WPF and found out, that unfortunately this does not work at all. There is the following error in the debug-output:
"Cannot find element that provides DataContext"
I suppose the resources aren't part of the visual tree in WPF whereas they are in Silverlight.
So - is my little trick possible in WPF as well?
Is my little trick to be considered a dirty hack?
What's your opinion and suggestions?
Regards
Johannes
Update:
as requested some more info - actually a minimal example:
XAML:
<Window x:Class="WpfDataContextInResources.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfDataContextInResources"
x:Name="window"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:TestWrapper x:Key="TestObj" />
</Window.Resources>
<StackPanel>
<TextBlock Text="{Binding Text}" />
<TextBlock Text="{Binding DataContext.Text, Source={StaticResource TestObj}, FallbackValue='FALLBACK'}" />
</StackPanel>
</Window>
the .cs file:
namespace WpfDataContextInResources
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new DataClass()
{
Text = "Hello",
};
}
}
public class TestWrapper : FrameworkElement {}
public class DataClass
{
public string Text { get; set; }
}
}
At least on my PC the lower text-block stays on the fallbackvalue
Update #2:
I tried the suggestion Matrin posted (deriving from DependencyObject, creating own DependencyProperty, etc) - it did not work either.
This time however the error-message is a different one:
"System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:(no path); DataItem=null; target element is 'TestWrapper' (HashCode=28415924); target property is 'TheData' (type 'Object')"
I also have some suggestions for workarounds though:
1.) - Use MultiBinding --> not compatible with Silverlight, not enough in some cases.
2.) - Use yet another wrapping object, set DataContext by hand in code-behind, like this --> fully compatible with Silverlight (apart from the fact, that you can't use a Framework-Element directly - you have to make an empty class deriving from it)
xaml:
<Window.Resources>
<FrameworkElement x:Key="DataContextWrapper" />
<local:TestWrapper x:Key="TestObj" DataContext="{Binding DataContext, Source={StaticResource DataContextWrapper}}" />
...
code behind:
//of course register this handler!
void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var dcw = this.Resources["DataContextWrapper"] as FrameworkElement;
dcw.DataContext = this.DataContext;
}
There may be a problem with your type being derived from FrameworkElement:
From the msdn page about suitable objects in ResourceDictionaries
[...] Being shareable is required [...]
Any object that is derived from the UIElement type is inherently not
shareable [...]
Derive from DependencyObject instead:
public class TestWrapper : DependencyObject {}

WPF Binding source is not DataContext as expected

I have a simple binding question as I feel I'm missing something fundamental in my view of how binding works.
I assume that since I've set the DataContext of my MainWindow to a ViewModel in code-behind, that all of the binding in MainWindow.xaml would assume source of this DataContext unless otherwise specified. This does not seem to be the case when I'm using my UserControl (which itself has a ViewModel driving it)
My scenario is best described in code:
MainWindow.xaml.cs
private ViewModels.MainMenuViewModel vm;
public MainWindow()
{
InitializeComponent();
vm = new ViewModels.MainMenuViewModel();
this.DataContext = vm;
}
MainWindow.xaml (using the data-context set in code-behind)
x:Class="Mediafour.Machine.EditorWPF.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:uc="clr-namespace:Machine.EditorWPF.Views"
xmlns:local="clr-namespace:Machine.EditorWPF"
xmlns:localVM="clr-namespace:Machine.EditorWPF.ViewModels"
Title="MainWindow" Height="350" Width="650">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<uc:MachineTreeView x:Name="MachineTreeView" Grid.Column="0" MachineDocument="{Binding Path=CurrentDocument}" />
MainWindowViewModel.cs
public class MainWindowViewModel : ObservableObject
{
public MainWindowViewModel()
{
OpenMachine(#"D:\Projects\Agnes\EditorWPF\Test.machine");
}
private void OpenMachine(string filePath)
{
MachineDocument currentDocument = MachineDocument.OpenFile(filePath);
CurrentDocument = currentDocument;
}
private MachineDocument _currentDocument;
public MachineDocument CurrentDocument
{
get { return _currentDocument; }
set
{
if (_currentDocument != null)
{
_currentDocument.Dispose();
_currentDocument = null;
}
_currentDocument = value;
base.RaisePropertyChanged("CurrentDocument"); //this fires
}
}
Using this approach, the binding statement in MainWindow.xaml errors out. Looking at Snoop binding error, it states that the CurrentDocument property is not found in MachineViewModel
System.Windows.Data Error: 40 : BindingExpression path error: 'CurrentDocument' property not found on 'object' ''MachineViewModel' (HashCode=27598891)'. BindingExpression:Path=CurrentDocument; DataItem='MachineViewModel' (HashCode=27598891); target element is 'MachineTreeView' (Name='MachineTreeView'); target property is 'MachineDocument' (type 'MachineDocument')
Why is it looking at the MachineViewModel when the binding is done in MainWindow?
Other binding properties in MainWindow do work as expected, this is the only UserControl binding I have though.
Either it is a simple mistake
you're setting MainMenuViewModel instead of MainWindowViewModel as MainWindow.DataContext
or maybe
you set the DataContext for the UserControl in the wrong manner. Take a look at this Simple Pattern for Creating Re-useable UserControls to do it the right way.

WPF DataGrid multiselect binding

I have a datagrid that is multi-select enabled. I need to change the selection in the viewmodel. However, the SelectedItems property is read only and can't be directly bound to a property in the viewmodel. So how do I signal to the view that the selection has changed?
Andy is correct. DataGridRow.IsSelected is a Dependency Property that can be databound to control selection from the ViewModel. The following sample code demonstrates this:
<Window x:Class="DataGridMultiSelectSample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tk="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"
Title="Window1" Height="300" Width="300">
<StackPanel>
<tk:DataGrid AutoGenerateColumns="False" ItemsSource="{Binding}" EnableRowVirtualization="False">
<tk:DataGrid.Columns>
<tk:DataGridTextColumn Header="Value" Binding="{Binding Value}" />
</tk:DataGrid.Columns>
<tk:DataGrid.RowStyle>
<Style TargetType="tk:DataGridRow">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</tk:DataGrid.RowStyle>
</tk:DataGrid>
<Button Content="Select Even" Click="Even_Click" />
<Button Content="Select Odd" Click="Odd_Click" />
</StackPanel>
</Window>
using System.ComponentModel;
using System.Windows;
namespace DataGridMultiSelectSample
{
public partial class Window1
{
public Window1()
{
InitializeComponent();
DataContext = new[]
{
new MyViewModel {Value = "Able"},
new MyViewModel {Value = "Baker"},
new MyViewModel {Value = "Charlie"},
new MyViewModel {Value = "Dog"},
new MyViewModel {Value = "Fox"},
};
}
private void Even_Click(object sender, RoutedEventArgs e)
{
var array = (MyViewModel[]) DataContext;
for (int i = 0; i < array.Length; ++i)
array[i].IsSelected = i%2 == 0;
}
private void Odd_Click(object sender, RoutedEventArgs e)
{
var array = (MyViewModel[])DataContext;
for (int i = 0; i < array.Length; ++i)
array[i].IsSelected = i % 2 == 1;
}
}
public class MyViewModel : INotifyPropertyChanged
{
public string Value { get; set; }
private bool mIsSelected;
public bool IsSelected
{
get { return mIsSelected; }
set
{
if (mIsSelected == value) return;
mIsSelected = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("IsSelected"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Be sure to set EnableRowVirtualisation="False" on the DataGrid element, else there's a risk that the IsSelected bindings fall out of kilter.
I haven't worked with the DataGrid much, but one technique that works for the ListView is to bind to the IsSelected property of the individual ListViewItem. Just set this to true for each object in your list, and then it will get selected.
Maybe the object that represents a row in the DataGrid also has an IsSelected property, and can be used in this way as well?
Guys, thanks for the help. My problem was solved. I think the problem is pretty common for new WPF developers, so I will restate my problem and as well as the solution in more details here just in case someone else runs into the same kind of problems.
The problem: I have a multi-select enabled datagrid of audio files. The grid has multiple column headers. The user can multi-select several row. When he clicks the Play button, the audio files will be played in the order of one the columns headers (say column A). When playback starts, the multi-select is cleared and only the currently playing file is highlighted. When playback is finished for all files, the multi-selection will be re-displayed. The playback is done in the viewmodel. As you can see, there are two problems here: 1) how to select the currently playing file from the viewmodel, and 2) how to signal to the view from the viewmodel that playback is finished and re-display the multi-selection.
The solution: To solve the first problem, I created a property in the viewmodel that is bound to the view's SelectedIndex property to select the currently playing file. To solve the second problem, I created a boolean property in the view model to indicate playback is finished. In the view's code behind, I subscribed the the boolean property's PropertyChanged event. In the event handler, the view's SelectedItems property is re-created from the saved multi-selection (the contents of SelectedItems was saved into a list and SelectedItems was cleared when playback started). At first, I had trouble re-creating SelectedItems. It turned out the problem was due to the fact that re-creation was initiated through a second thread. WPF does not allow that. The solution to this is to use the Dispatcher.Invoke() to let the main thread do the work. This may be a very simple problem for experienced developers, but for newbies, it's a small challenge. Anyway, a lot of help from different people.
Just use SelectedItems on any MultiSelector derived class , and use methods Add, Remove, Clear on IList it returns .

How to set RichTextBox Font for the next text to be written?

I need to set the font family for the next text to be written in a RichTextBox.
I tried setting that with...
<RichTextBox x:Name="RichTextEditor" MaxWidth="1000" SpellCheck.IsEnabled="True"
FontFamily="{Binding ElementName=TextFontComboBox, Path=SelectedItem}"
FontSize="{Binding ElementName=TextSizeComboBox, Path=SelectedValue}"
Width="Auto" Height="Auto" HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto" />
...but it changed the whole text. I suppose that with the Selection property I can restrict the change to be applied just to the selected area. But how for the next -not yet typed- text?
In order to set the FontFamily based on the cursor position you need to define a custom control with a dependency property that helps insert a new Run section by overriding the OnTextInput method.
I included most of the code, you'll need to modify the namespaces to fit your development environment.
The code uses a ViewModel to manage the available fonts and manage if the font changed.
This code is only a prototype and does not deal with focusing issues between the two controls.
To use this code:
1- Type some text in the RichTectBox.
2- Change the font in the ComboBox.
3- Tab back to the RichTextBox.
4- Type some more text.
Here is the custom RichTextBox control:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
namespace RichTextboxFont.Views
{
public class RichTextBoxCustom : RichTextBox
{
public static readonly DependencyProperty CurrentFontFamilyProperty =
DependencyProperty.Register("CurrentFontFamily",
typeof(FontFamily), typeof
(RichTextBoxCustom),
new FrameworkPropertyMetadata(new FontFamily("Tahoma"),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(OnCurrentFontChanged)));
public FontFamily CurrentFontFamily
{
get
{
return (FontFamily)GetValue(CurrentFontFamilyProperty);
}
set
{
SetValue(CurrentFontFamilyProperty, value);
}
}
private static void OnCurrentFontChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{}
protected override void OnTextInput(TextCompositionEventArgs e)
{
ViewModels.MainViewModel mwvm = this.DataContext as ViewModels.MainViewModel;
if ((mwvm != null) && (mwvm.FontChanged))
{
TextPointer textPointer = this.CaretPosition.GetInsertionPosition(LogicalDirection.Forward);
Run run = new Run(e.Text, textPointer);
run.FontFamily = this.CurrentFontFamily;
this.CaretPosition = run.ElementEnd;
mwvm.FontChanged = false;
}
else
{
base.OnTextInput(e);
}
}
}
}
Here is the XAML:
<Window x:Class="RichTextboxFont.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:RichTextboxFont.Views"
xmlns:ViewModels="clr-namespace:RichTextboxFont.ViewModels"
Title="Main Window"
Height="400" Width="800">
<DockPanel>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ComboBox ItemsSource="{Binding Path=Fonts}"
SelectedItem="{Binding Path=SelectedFont, Mode=TwoWay}"/>
<local:RichTextBoxCustom Grid.Row="1"
CurrentFontFamily="{Binding Path=SelectedFont, Mode=TwoWay}"
FontSize="30"/>
</Grid>
</DockPanel>
</Window>
Here is the ViewModel:
If you do not use view models, let me know and I'll add the base class code too; otherwise, google/stackoverflow can help you too.
using System.Collections.ObjectModel;
using System.Windows.Media;
namespace RichTextboxFont.ViewModels
{
public class MainViewModel : ViewModelBase
{
#region Constructor
public MainViewModel()
{
FontFamily f1 = new FontFamily("Georgia");
_fonts.Add(f1);
FontFamily f2 = new FontFamily("Tahoma");
_fonts.Add(f2);
}
private ObservableCollection<FontFamily> _fonts = new ObservableCollection<FontFamily>();
public ObservableCollection<FontFamily> Fonts
{
get
{
return _fonts;
}
set
{
_fonts = value;
OnPropertyChanged("Fonts");
}
}
private FontFamily _selectedFont = new FontFamily("Tahoma");
public FontFamily SelectedFont
{
get
{
return _selectedFont;
}
set
{
_selectedFont = value;
FontChanged = true;
OnPropertyChanged("SelectedFont");
}
}
private bool _fontChanged = false;
public bool FontChanged
{
get
{
return _fontChanged;
}
set
{
_fontChanged = value;
OnPropertyChanged("FontChanged");
}
}
#endregion
}
}
Here is the Window code-behind where I initialise the ViewModel:
using System.Windows;
namespace RichTextboxFont.Views
{
public partial class MainView : Window
{
public MainView()
{
InitializeComponent();
this.DataContext = new ViewModels.MainViewModel();
}
}
}
There's a much easier way to do this: Implement a toolbar for your RichTextBox.
Unlike WinForms, the RichTextBox in WPF doesn't come with a toolbar by default, but it's really easy to create one yourself. The RichTextBox automatically handles many EditingCommands, so it's just a matter of creating a toolbar and some buttons. Microsoft has provided sample code for this at the bottom of the RichTextBox Overview on MSDN.
Unfortunately, those editing commands don't include setting the FontFace property of the selection, though you can create a ComboBox on the toolbar that can trigger the change with an event handler in the codebehind file.
That's the approach taken in this CodePlex article by Gregor Pross: WPF RichTextEditor
The project is commented in German, but the source itself is very clearly written. The codebehind used for his font selector ComboBox looks like this:
private void Fonttype_DropDownClosed(object sender, EventArgs e)
{
string fontName = (string)Fonttype.SelectedItem;
if (fontName != null)
{
RichTextControl.Selection.ApplyPropertyValue(System.Windows.Controls.RichTextBox.FontFamilyProperty, fontName);
RichTextControl.Focus();
}
}
The main reason that people struggle with the FontFace selection is that after the font selection has been made, you must return focus to the RichTextBox. If the user must manually press tab or click into the RichTextBox, a new text selection gets created and you lose the formatting options you've chosen.
One of the answers to this StackOverflow question discusses that problem.
WPF Richtextbox FontFace/FontSize
This isn't exactly a trivial answer.
To do inline text formatting in a Rich TextBox like you want you will have to modify the Document property of the RichTextBox. Very simply, something like this will work
<RichTextBox >
<RichTextBox.Document>
<FlowDocument>
<Paragraph>
<Run>Something</Run>
<Run FontWeight="Bold">Something Else</Run>
</Paragraph>
</FlowDocument>
</RichTextBox.Document>
</RichTextBox>
I think you could create a custom Control that creates a new block element and sets the font properties you need based on the user input.
For example, If the user types something then presses bold. You would want to wrap the previous text in a run and create a new run element setting the FontWeight to bold then the subsequent text will be wrapped in the bolded run.
Again, not a trivial solution but I can't think of any other way to accomplish what you are after.

WPF MVVM : Commands are easy. How to Connect View and ViewModel with RoutedEvent

Suppose I have a view implemented as a DataTempate inside a resource Dictionary.
And I have a corresponding ViewModel.
Binding Commands are easy. But what if my View contains a control such as a ListBox, and I need to Publish an application wide event (Using Prism's Event Aggreagtor) based on the Item being Changed on the List.
if ListBox supports a command I could just bind it to a command in the ViewModel and publish the event. But Listbox doesn't allow such an option.
How do I bridge this?
EDIT:
Many great answers.
Take a look at this link http://blogs.microsoft.co.il/blogs/tomershamam/archive/2009/04/14/wpf-commands-everywhere.aspx
Thanks
Ariel
Instead of trying to bind a command to when the item changes, I looked at the problem another way.
If you bind the selected item of the ListBox to a property in the ViewModel, then when that property is changed you can publish the event. That way the ViewModel remains the source of the event and it is triggered by the item changing, which is what you want.
<ListBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" />
...
public class ViewModel
{
public IEnumerable<Item> Items { get; set; }
private Item selectedItem;
public Item SelectedItem
{
get { return selectedItem; }
set
{
if (selectedItem == value)
return;
selectedItem = value;
// Publish event when the selected item changes
}
}
Extend the control to support ICommandSource and decide which action should trigger the command.
I did this with Combo Box and used OnSelectionChanged as the trigger for the command. First I will show in XAML how I bind the command to the extended Control ComboBox which I called CommandComboBox, then I will show the code for CommandComboBox that adds the support for the ICommandSource to ComboBox.
1) Using CommandComboBox in your XAML code:
In your XAML namespace declarations include
xmlns:custom="clr-namespace:WpfCommandControlsLibrary;assembly=WpfCommandControlsLibrary">
Use the CommandComboBox in place of ComboBox and bind the command to it like so: Note that in this example I have a defined a command called SetLanguageCommand im my ViewModel and I am passing the selected value for this ComboBox as the parameter to the command.
<custom:CommandComboBox
x:Name="ux_cbSelectLanguage"
ItemsSource="{Binding Path = ImagesAndCultures}"
ItemTemplate="{DynamicResource LanguageComboBoxTemplate}"
Command="{Binding Path=SetLanguageCommand, Mode=Default}"
CommandParameter="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=SelectedValue, Mode=Default}"
IsSynchronizedWithCurrentItem="True"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Grid.Column="1" Margin="0,0,20,0" Style="{DynamicResource GlassyComboBox}" ScrollViewer.IsDeferredScrollingEnabled="True"
/>
2) The code for CommandComboBox
The code for the file CommandComboBox.cs is included below. I added this file to a Class Library called WpfCommandControlsLibrary and made it a separate project so I could easily add any extend commands to whatever solution needed to use them and so I could easily add additional WPF Controls and extend them to support the ICommandSource inteface.
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace WpfCommandControlsLibrary
{
/// <summary>
/// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
///
/// Step 1a) Using this custom control in a XAML file that exists in the current project.
/// Add this XmlNamespace attribute to the root element of the markup file where it is
/// to be used:
///
/// xmlns:MyNamespace="clr-namespace:WpfCommandControlsLibrary"
///
///
/// Step 1b) Using this custom control in a XAML file that exists in a different project.
/// Add this XmlNamespace attribute to the root element of the markup file where it is
/// to be used:
///
/// xmlns:MyNamespace="clr-namespace:WpfCommandControlsLibrary;assembly=WpfCommandControlsLibrary"
///
/// You will also need to add a project reference from the project where the XAML file lives
/// to this project and Rebuild to avoid compilation errors:
///
/// Right click on the target project in the Solution Explorer and
/// "Add Reference"->"Projects"->[Select this project]
///
///
/// Step 2)
/// Go ahead and use your control in the XAML file.
///
/// <MyNamespace:CustomControl1/>
///
/// </summary>
public class CommandComboBox : ComboBox, ICommandSource
{
public CommandComboBox() : base()
{
}
#region Dependency Properties
// Make Command a dependency property so it can use databinding.
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register(
"Command",
typeof(ICommand),
typeof(CommandComboBox),
new PropertyMetadata((ICommand)null,
new PropertyChangedCallback(CommandChanged)));
public ICommand Command
{
get
{
return (ICommand)GetValue(CommandProperty);
}
set
{
SetValue(CommandProperty, value);
}
}
// Make CommandTarget a dependency property so it can use databinding.
public static readonly DependencyProperty CommandTargetProperty =
DependencyProperty.Register(
"CommandTarget",
typeof(IInputElement),
typeof(CommandComboBox),
new PropertyMetadata((IInputElement)null));
public IInputElement CommandTarget
{
get
{
return (IInputElement)GetValue(CommandTargetProperty);
}
set
{
SetValue(CommandTargetProperty, value);
}
}
// Make CommandParameter a dependency property so it can use databinding.
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register(
"CommandParameter",
typeof(object),
typeof(CommandComboBox),
new PropertyMetadata((object)null));
public object CommandParameter
{
get
{
return (object)GetValue(CommandParameterProperty);
}
set
{
SetValue(CommandParameterProperty, value);
}
}
#endregion
// Command dependency property change callback.
private static void CommandChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
CommandComboBox cb = (CommandComboBox)d;
cb.HookUpCommand((ICommand)e.OldValue, (ICommand)e.NewValue);
}
// Add a new command to the Command Property.
private void HookUpCommand(ICommand oldCommand, ICommand newCommand)
{
// If oldCommand is not null, then we need to remove the handlers.
if (oldCommand != null)
{
RemoveCommand(oldCommand, newCommand);
}
AddCommand(oldCommand, newCommand);
}
// Remove an old command from the Command Property.
private void RemoveCommand(ICommand oldCommand, ICommand newCommand)
{
EventHandler handler = CanExecuteChanged;
oldCommand.CanExecuteChanged -= handler;
}
// Add the command.
private void AddCommand(ICommand oldCommand, ICommand newCommand)
{
EventHandler handler = new EventHandler(CanExecuteChanged);
canExecuteChangedHandler = handler;
if (newCommand != null)
{
newCommand.CanExecuteChanged += canExecuteChangedHandler;
}
}
private void CanExecuteChanged(object sender, EventArgs e)
{
if (this.Command != null)
{
RoutedCommand command = this.Command as RoutedCommand;
// If a RoutedCommand.
if (command != null)
{
if (command.CanExecute(CommandParameter, CommandTarget))
{
this.IsEnabled = true;
}
else
{
this.IsEnabled = false;
}
}
// If a not RoutedCommand.
else
{
if (Command.CanExecute(CommandParameter))
{
this.IsEnabled = true;
}
else
{
this.IsEnabled = false;
}
}
}
}
// If Command is defined, selecting a combo box item will invoke the command;
// Otherwise, combo box will behave normally.
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
if (this.Command != null)
{
RoutedCommand command = Command as RoutedCommand;
if (command != null)
{
command.Execute(CommandParameter, CommandTarget);
}
else
{
((ICommand)Command).Execute(CommandParameter);
}
}
}
// Keep a copy of the handler so it doesn't get garbage collected.
private static EventHandler canExecuteChangedHandler;
}
}
One option is to extend the control in question and add support for the particular command you require. For example, I've modified ListView before to support the ItemActivated event and related command.
Well, nobody answered.
So I've gave up and moved the implementation of the View outside the Dictionary into a regular UserControl, I've injected him a reference to the ViewModel.
Now when the ListBox fire the Event it's calls the ViewModel and from there everything is possible again.
Ariel
A great solution to this type of problem comes from the usage of Attached Properties.
Marlon Grech has taken the usage of Attached Properties to the next level by creating Attached Command Behaviors. Using these it is possible to bind any Command existing in a ViewModel to any Event existing in the view.
This is something I use a lot to deal with similar issues with ListBoxes, where I want them to open, or edit or do some action on a double click.
In this example I'm using an older version of Attached Command Behaviors, but the effect is the same. I have a style that is used for ListBoxItems which I am explicitly keying to.
However, it would be easy enough to create a application or window wide style applying to all ListBoxItems that sets the commands at a much higher level. Then, whenever the event for the ListBoxItem attached to the CommandBehavior.Event property would fire, it instead fires off the attached Command.
<!-- acb is the namespace reference to the Attached Command Behaviors -->
<Style x:Key="Local_OpenListItemCommandStyle">
<Setter Property="acb:CommandBehavior.Event"
Value="MouseDoubleClick" />
<Setter Property="acb:CommandBehavior.Command"
Value="{Binding ElementName=uiMyListBorder, Path=DataContext.OpenListItemCommand}" />
<Setter Property="acb:CommandBehavior.CommandParameter"
Value="{Binding}" />
</Style>
<DataTemplate x:Key="MyView">
<Border x:Name="uiMyListBorder">
<ListBox ItemsSource="{Binding MyItems}"
ItemContainerStyle="{StaticResource local_OpenListItemCommandStyle}" />
</Border>
</DataTemplate>
I have been writing behaviors (attached properties) to do this, and there are still cases where I need them.
For the usual case however, simply binding an event to a command, you can do everything in Xaml if you have Blend SDK 4 installed. Note that you will have to add a reference to System.Windows.Interactivity.dll, and to redistribute this assembly.
Expression Blend SDK for .NET 4
Microsoft SDKs (for future reference)
This example is invoking an ICommand DragEnterCommand on the ViewModel when the DragEnter event of the Grid is fired:
<UserControl xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" >
<Grid>
<i:Interaction.Triggers>
<i:EventTrigger EventName="DragEnter">
<i:InvokeCommandAction Command="{Binding DragEnterCommand}" CommandParameter="{Binding ...}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Grid>
</UserControl>
Try using Prism 2.
It comes with great extensions to commanding and opens many new posibilites (like commands to being tied to visual tree).

Resources