I am trying to display a login window once my MainWindow loads while sticking to the MVVM pattern. So I am trying to Bind my main windows Loaded event to an event in my viewmodel.
Here is what I have tried:
MainWindowView.xaml
<Window x:Class="ScrumManagementClient.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"
DataContext="ViewModel.MainWindowViewModel"
Loaded="{Binding ShowLogInWindow}">
<Grid>
</Grid>
</Window>
MainWindowViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ScrumManagementClient.ViewModel
{
class MainWindowViewModel : ViewModelBase
{
public void ShowLogInWindow(object sender, EventArgs e)
{
int i = 0;
}
}
}
The error message I am getting is "Loaded="{Binding ShowLogInWindow}" is not valid. '{Binding ShowLogInWindow}' is not a valid event handler method name. Only instance methods on the generated or code-behind class are valid."
You're going to have to use the System.Windows.Interactivity dll.
Then add the namespace in your XAML:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Then you can do stuff like:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding MyICommandThatShouldHandleLoaded}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Please note that you will have to use an ICommand (or DelegateCommand is you use Prism, or RelayCommand if you use MVVMLight), and the DataContext of your Window must hold that ICommand.
Use Attached Behavior. That is allowed in MVVM ....
(code below may / may not compile just like that)
XAML ...
<Window x:Class="..."
...
xmlns:local="... namespace of the attached behavior class ..."
local:MyAttachedBehaviors.LoadedCommand="{Binding ShowLogInWindowCommand}">
<Grid>
</Grid>
</Window>
Code Behind...
class MainWindowViewModel : ViewModelBase
{
private ICommand _showLogInWindowCommand;
public ICommand ShowLogInWindowCommand
{
get
{
if (_showLogInWindowCommand == null)
{
_showLogInWindowCommand = new DelegateCommand(OnLoaded)
}
return _showLogInWindowCommand;
}
}
private void OnLoaded()
{
//// Put all your code here....
}
}
And the attached behavior...
public static class MyAttachedBehaviors
{
public static DependencyProperty LoadedCommandProperty
= DependencyProperty.RegisterAttached(
"LoadedCommand",
typeof(ICommand),
typeof(MyAttachedBehaviors),
new PropertyMetadata(null, OnLoadedCommandChanged));
private static void OnLoadedCommandChanged
(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
var frameworkElement = depObj as FrameworkElement;
if (frameworkElement != null && e.NewValue is ICommand)
{
frameworkElement.Loaded
+= (o, args) =>
{
(e.NewValue as ICommand).Execute(null);
};
}
}
public static ICommand GetLoadedCommand(DependencyObject depObj)
{
return (ICommand)depObj.GetValue(LoadedCommandProperty);
}
public static void SetLoadedCommand(
DependencyObject depObj,
ICommand value)
{
depObj.SetValue(LoadedCommandProperty, value);
}
}
DelegateCommand source code can be found on the internet... Its the most suited ICommand API available for MVVM.
edit:19.07.2016 two minor syntax errors fixed
Update:
I made a post about a new more flexible version of the method binding that uses a slightly different syntax here:
http://www.singulink.com/CodeIndex/post/updated-ultimate-wpf-event-method-binding
The full code listing is available here:
https://gist.github.com/mikernet/7eb18408ffbcc149f1d9b89d9483fc19
Any future updates will be posted to the blog so I suggest checking there for the latest version.
Original Answer:
.NET 4.5+ supports markup extensions on events now. I used this to create a method binding that can be used like this:
<!-- Basic usage -->
<Button Click="{data:MethodBinding OpenFromFile}" Content="Open" />
<!-- Pass in a binding as a method argument -->
<Button Click="{data:MethodBinding Save, {Binding CurrentItem}}" Content="Save" />
<!-- Another example of a binding, but this time to a property on another element -->
<ComboBox x:Name="ExistingItems" ItemsSource="{Binding ExistingItems}" />
<Button Click="{data:MethodBinding Edit, {Binding SelectedItem, ElementName=ExistingItems}}" />
<!-- Pass in a hard-coded method argument, XAML string automatically converted to the proper type -->
<ToggleButton Checked="{data:MethodBinding SetWebServiceState, True}"
Content="Web Service"
Unchecked="{data:MethodBinding SetWebServiceState, False}" />
<!-- Pass in sender, and match method signature automatically -->
<Canvas PreviewMouseDown="{data:MethodBinding SetCurrentElement, {data:EventSender}, ThrowOnMethodMissing=False}">
<controls:DesignerElementTypeA />
<controls:DesignerElementTypeB />
<controls:DesignerElementTypeC />
</Canvas>
<!-- Pass in EventArgs -->
<Canvas MouseDown="{data:MethodBinding StartDrawing, {data:EventArgs}}"
MouseMove="{data:MethodBinding AddDrawingPoint, {data:EventArgs}}"
MouseUp="{data:MethodBinding EndDrawing, {data:EventArgs}}" />
<!-- Support binding to methods further in a property path -->
<Button Content="SaveDocument" Click="{data:MethodBinding CurrentDocument.DocumentService.Save, {Binding CurrentDocument}}" />
View model method signatures:
public void OpenFromFile();
public void Save(DocumentModel model);
public void Edit(DocumentModel model);
public void SetWebServiceState(bool state);
public void SetCurrentElement(DesignerElementTypeA element);
public void SetCurrentElement(DesignerElementTypeB element);
public void SetCurrentElement(DesignerElementTypeC element);
public void StartDrawing(MouseEventArgs e);
public void AddDrawingPoint(MouseEventArgs e);
public void EndDrawing(MouseEventArgs e);
public class Document
{
// Fetches the document service for handling this document
public DocumentService DocumentService { get; }
}
public class DocumentService
{
public void Save(Document document);
}
More details can be found here: http://www.singulink.com/CodeIndex/post/building-the-ultimate-wpf-event-method-binding-extension
The full class code is available here:
https://gist.github.com/mikernet/4336eaa8ad71cb0f2e35d65ac8e8e161
A more generic way using behaviors is proposed at AttachedCommandBehavior V2 aka ACB and it even supports multiple event-to-command bindings,
Here is a very basic example of use:
<Window x:Class="Example.YourWindow"
xmlns:local="clr-namespace:AttachedCommandBehavior;assembly=AttachedCommandBehavior"
local:CommandBehavior.Event="Loaded"
local:CommandBehavior.Command="{Binding DoSomethingWhenWindowIsLoaded}"
local:CommandBehavior.CommandParameter="Some information"
/>
For VS 2013 Update 5 I wasn't able to get around "Unable to cast object of type 'System.Reflection.RuntimeEventInfo' to type 'System.Reflection.MethodInfo". Instead in a "Core" directory I made a simple interface
interface ILoad
{
void load();
}
My viewModel already had the load() function implementing ILoad. In my .xaml.cs I call the ViewModel load() through ILoad.
private void ml_Loaded(object sender, RoutedEventArgs e)
{
(this.ml.DataContext as Core.ILoad).load();
}
The xaml.cs knows nothing about the ViewModel except the POCO ILoad, the ViewModel knows nothing about the xaml.cs. The ml_loaded event is mapped to ViewModel load().
Related
I come from a WPF background so I thought I'd experiment with building a to-do app in WinUI 3. The app structure is a little overdesigned as I'm trying build it out like a more complex app. For that reason I have a ToDoTaskView and ToDoTaskViewModel, along with a MainWindowView and MainWindowViewModel even though it'd be much easier to build the entire app in a single XAML file.
The ToDoTaskView has a delete button, but the delete command lives on the MainWindowViewModel, as that's where the list that it must be deleted from lives. I think this a pretty common pattern where a sub-view needs to send a command to a parent view model.
The (abridged) MainWindowView:
<UserControl>
<ItemsRepeater ItemsSource="{Binding Tasks}">
<DataTemplate>
<local:ToDoTaskView />
</DataTemplate>
</ItemsRepeater>
</UserControl>
And the (heavily abridged) ToDoTaskView:
<UserControl>
<Button Command="???">Delete</Button>
</UserControl>
In WPF there's many ways to deal with this.
RoutedCommand
My prefered method. The MainWindowView can listen for a custom ToDoTaskDeleted routed command and bind to the command on the view model. Then any UI element anywhere underneath MainWindowView can fire said event and rest easy knowing it'll be handled somewhere above it on the visual tree.
There's no RoutedCommand in WinUI 3, and even worse, routed events are locked down and you can't define custom ones. So even building a custom RoutedCommand implementation would be difficult.
DynamicResource
I can define a StandardUICommand in MainWindowView.Resources, bind it to the command in the view model, then in ToDoTaskView I can use {DynamicResource DeleteCommand} to have the resource system search up the visual tree for the command.
Except I can't. WinUI3 doesn't have DynamicResource, only StaticResource. And since the two views are in different XAML files, and ToDoTaskView in a templated context, StaticResource can't resolve the resource name between them.
I think this could work for resources in App.xaml, but I'd rather not shove every command into the top level scope instead of keeping them where they belong.
All the commanding examples in the Microsoft docs seem to assume that the button and handler are in the same file, or they directly pass a reference to the command through to the child view's DataContext.
RelativeAncestor
Peter below reminded me that I tried this too, and found it's missing in WinUI 3. RelativeSource doesn't support any kind of ancestor discovery.
Manual Kludge
Setting up a direct reference from ToDoTaskViewModel to MainWindowViewModel is certainly possible, but I hate it. After all, who's to guarantee that this particular to do item is part of a list at any one moment? Maybe it lives in a pop-up dialog as a reminder? Handling this kind of thing through the visual tree is the Correct(tm) way to do it.
I wouldn't accept a PR from a coworker on my WPF project with this solution. But I can't seem to find any better way in WinUI 3.
Have I missed something about WinUI 3? Is it just not mature enough yet to have a solution? It seems like this scenario isn't so uncommon that it would be completely unsupported.
In this case, I'd create an ICommand dependency property, DeleteCommand and and bind a command in the view model. Here's a sample code using the CommunityToolkit.Mvvm NuGet package.
MainWindow.xaml
The MainWindow is named, "ThisWindow" in this case, so we can access its ViewModel from the ItemTemplate.
The DeleteCommandParameter is bound to the DataContext of the item, ToDoTaskViewModel in this case.
<Window
x:Class="ToDoApp.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:local="using:ToDoApp"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="ThisWindow"
mc:Ignorable="d">
<Grid RowDefinitions="Auto,*">
<StackPanel
Grid.Row="0"
Orientation="Horizontal">
<TextBox x:Name="NewToDo" />
<Button
Command="{x:Bind ViewModel.AddToDoCommand}"
CommandParameter="{x:Bind NewToDo.Text, Mode=OneWay}"
Content="Add" />
</StackPanel>
<ScrollViewer Grid.Row="1">
<ItemsRepeater ItemsSource="{x:Bind ViewModel.ToDoTasks, Mode=OneWay}">
<ItemsRepeater.ItemTemplate>
<DataTemplate x:DataType="local:ToDoTaskViewModel">
<local:ToDoTaskView
DeleteCommand="{Binding ElementName=ThisWindow, Path=ViewModel.DeleteToDoCommand}"
DeleteCommandParameter="{x:Bind}"
ToDo="{x:Bind ToDo, Mode=OneWay}" />
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</ScrollViewer>
</Grid>
</Window>
MainWindow.xaml.cs
using Microsoft.UI.Xaml;
namespace ToDoApp;
public sealed partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
}
public MainWindowViewModel ViewModel { get; } = new();
}
MainWindowViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;
namespace ToDoApp;
[ObservableObject]
public partial class MainWindowViewModel
{
[ObservableProperty]
private ObservableCollection<ToDoTaskViewModel> toDoTasks = new();
[RelayCommand]
private void AddToDo(string todo)
{
ToDoTasks.Add(new ToDoTaskViewModel() { ToDo = todo });
}
[RelayCommand]
private void DeleteToDo(ToDoTaskViewModel toDoTask)
{
ToDoTasks.Remove(toDoTask);
}
}
ToDoTaskView.xaml
<UserControl
x:Class="ToDoApp.ToDoTaskView"
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="using:ToDoApp"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid ColumnDefinitions="*,Auto">
<TextBlock
Grid.Column="0"
Text="{x:Bind ToDo, Mode=OneWay}" />
<Button
Grid.Column="1"
Command="{x:Bind DeleteCommand, Mode=OneWay}"
CommandParameter="{x:Bind DeleteCommandParameter, Mode=OneWay}"
Content="Delete" />
</Grid>
</UserControl>
ToDoTaskView.xaml.cs
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System.Windows.Input;
namespace ToDoApp;
public sealed partial class ToDoTaskView : UserControl
{
public static readonly DependencyProperty ToDoProperty = DependencyProperty.Register(
nameof(ToDo),
typeof(string),
typeof(ToDoTaskView),
new PropertyMetadata(default));
public static readonly DependencyProperty DeleteCommandProperty = DependencyProperty.Register(
nameof(DeleteCommand),
typeof(ICommand),
typeof(ToDoTaskView),
new PropertyMetadata(default));
public static readonly DependencyProperty DeleteCommandParameterProperty = DependencyProperty.Register(
nameof(DeleteCommandParameter),
typeof(object),
typeof(ToDoTaskView),
new PropertyMetadata(default));
public ToDoTaskView()
{
this.InitializeComponent();
}
public string ToDo
{
get => (string)GetValue(ToDoProperty);
set => SetValue(ToDoProperty, value);
}
public ICommand DeleteCommand
{
get => (ICommand)GetValue(DeleteCommandProperty);
set => SetValue(DeleteCommandProperty, value);
}
public object DeleteCommandParameter
{
get => (object)GetValue(DeleteCommandParameterProperty);
set => SetValue(DeleteCommandParameterProperty, value);
}
}
ToDoTaskViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
namespace ToDoApp;
[ObservableObject]
public partial class ToDoTaskViewModel
{
[ObservableProperty]
private string toDo = string.Empty;
}
Ok I have a solution. I cannot emphasize enough how much of a disgusting hack this is. Normally I'd be embarrassed to post this, but the only ones who should be embarrassed are Microsoft for publishing Win UI 3 in its current state and claiming it's capable of making real applications.
The gist of this is to mimic Ancestor-type RelativeSource binding in WPF. We create two attached properties - ParentContextViewType to specify the type of the ancestor we're looking for - and ParentContextView which is automatically assigned a reference to the desired parent view instance when the child loads. (I'd have made ParentContextView a readonly property, but of course, Win UI doesn't support that...) Then for the child button, we do a RelativeSource Self binding to the attached ParentContextView property, then adding the rest of the path, just like we would with a legit ancestor type bind.
Here goes (and may god have mercy on my soul):
using System;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
namespace ParentBinding
{
public static class Hacks
{
public static DependencyProperty ParentContextView =
DependencyProperty.RegisterAttached(
"ParentContextView",
typeof(FrameworkElement),
typeof(Hacks),
new PropertyMetadata(null));
public static FrameworkElement GetParentContextView(DependencyObject d)
{
return d.GetValue(ParentContextView) as FrameworkElement;
}
public static void SetParentContextView(DependencyObject d, FrameworkElement view)
{
d.SetValue(ParentContextView, view);
}
public static DependencyProperty ParentContextViewTypeProperty =
DependencyProperty.RegisterAttached(
"ParentContextViewType",
typeof(Type),
typeof(Hacks),
new PropertyMetadata(null, (d, e) =>
{
if (!(d is FrameworkElement fe))
return;
if (e.OldValue != null)
fe.Loaded -= OnParentContextFeLoaded;
if (e.NewValue != null)
fe.Loaded += OnParentContextFeLoaded;
}));
private static void OnParentContextFeLoaded(object sender, RoutedEventArgs e)
{
if (!(sender is FrameworkElement fe))
return;
var type = GetParentContextViewType(fe);
if (type == null)
return;
while (!type.IsAssignableFrom(fe.GetType()) &&
(fe = VisualTreeHelper.GetParent(fe) as FrameworkElement) != null)
{
}
SetParentContextView(sender as DependencyObject, fe);
}
public static Type GetParentContextViewType(DependencyObject d)
{
return d.GetValue(ParentContextViewTypeProperty) as Type;
}
public static void SetParentContextViewType(DependencyObject d, Type val)
{
d.SetValue(ParentContextViewTypeProperty, val);
}
}
}
A use-case:
Model stuff:
using Microsoft.UI.Xaml.Input;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace ParentBinding
{
public class Command : ICommand
{
Action<object> _action;
public Command(Action<object> action)
{
_action = action;
}
public event EventHandler? CanExecuteChanged;
public bool CanExecute(object? parameter) => true;
public void Execute(object? parameter)
{
_action?.Invoke(parameter);
}
}
public class Parent
{
public ObservableCollection<Child> Children { get; set; }
private Command _deleteChildCommand;
public ICommand DeleteChildCommand =>
_deleteChildCommand ?? (_deleteChildCommand = new Command((p) =>
{
if (!(p is Child ch))
return;
this.Children.Remove(ch);
}));
}
public class Child
{
public string Name { get; set; }
public override string ToString() => this.Name;
}
}
Main Window:
<Window x:Class="ParentBinding.MainWindow"
x:Name="_main"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ParentBinding"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<ListView DataContext="{Binding ElementName=_main, Path=Parent}"
ItemsSource="{Binding Children}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Child">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" />
<Button local:Hacks.ParentContextViewType="ListView"
Grid.Column="1"
CommandParameter="{Binding}"
Content="Delete"
Command="{Binding
Path=(local:Hacks.ParentContextView).DataContext.DeleteChildCommand,
RelativeSource={RelativeSource Self}}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Window>
using Microsoft.UI.Xaml;
namespace ParentBinding
{
public sealed partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
}
public Parent Parent { get; } = new Parent
{
Children = new System.Collections.ObjectModel.ObservableCollection<Child>
{
new Child
{
Name = "Larry"
},
new Child
{
Name = "Curly"
},
new Child
{
Name = "Moe"
}
}
};
}
}
Amazingly, it works, and one of the reasons I was so curious to try it and post it is that it is, more or less, a general purpose substitute for ancestor type binding in WinUI 3. Hope someone finds it useful.
I have created custome window (titlebar, min/max/ext buttons, own border for window manipulation and lots of styles and triggers).
There are 5 methods defined (which i would like to override):
From window markup:
SourceInitialized="Window_SourceInitialized"
Closing="Window_Closing"
From Titlebar buttons:
Exit_Click()
Max_Click()
Min_Click()
And at last I have DockPanel
<DockPanel Name="ClientArea"/>
In which I want to put my content
I have tried to add content from code:
BaseWindow editInterfaceWindow = new BaseWindow() { Owner = this };
editInterfaceWindow.DataContext = new EditInterface();
editInterfaceWindow.ShowDialog();
But this way some bindings stoped working and inside editInterfaceWindow I cant create another window this way because of Owner = this. There are also some problems with InitializeComponent() in constructor.
And ListView inside EditInterface UserControl <ListView Name="LBAvaliable" ItemsSource="{Binding AvaliableFaces, UpdateSourceTrigger=PropertyChanged}"> is not visible in code as LBAvaliable.
I have used that window few times, filling ClientArea with content by hand.
How should I create other windows, so that I can just inherit it or just define binding? So my XAML for every single window does not take ~1000 lines of code.
In the past I've used MVVMCross Framework and we never had to worry about this ourselves. Though this is not the best, here's an idea on what you can do.
Create a view model that can be overridden for your user control.
Set data templates.
Programmatically change the view model for your user control's main content and let data templates do the work for the UI.
View Model: Pre-defined 3 button actions ready for you to set/override.
public class MainUCViewModel : ViewModelBase
{
private Action<object> btnACommand;
private Action<object> btnBCommand;
private Action<object> btnCCommand;
private object ccVM;
public ViewModelBase CCVM
{
get { return this.ccVM; }
set
{
this.ccVM = value;
OnPropertyChanged(); // Notify View
}
}
public MainUCViewModel()
{
}
public RelayCommand BtnACommand
{
get { return new RelayCommand(btnACommand); }
}
public RelayCommand BtnBCommand
{
get { return new RelayCommand(btnBCommand); }
}
public RelayCommand BtnCCommand
{
get { return new RelayCommand(btnCCommand); }
}
public void SetBtnACommand(Action<object> action)
{
this.btnACommand = action;
}
public void SetBtnBCommand(Action<object> action)
{
this.btnBCommand = action;
}
public void SetBtnCCommand(Action<object> action)
{
this.btnCCommand = action;
}
}
View:
<UserControl x:Class="WpfApplication1.Views.UserControls.MainUC"
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="500" d:DesignWidth="750">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="45" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<StackPanel Orientation="Horizontal">
<Button Command="{Binding BtnACommand}" Width="100">
<TextBlock>A</TextBlock>
</Button>
<Rectangle Width="15" />
<Button Command="{Binding BtnBCommand}" Width="100">
<TextBlock>B</TextBlock>
</Button>
<Rectangle Width="15" />
<Button Command="{Binding BtnCCommand}" Width="100">
<TextBlock>C</TextBlock>
</Button>
</StackPanel>
</Grid>
<Grid Grid.Row="1">
<ContentControl x:Name="CCMain" Content="{Binding CCVM}"/>
</Grid>
</Grid>
</UserControl>
Look at Thinking with MVVM: Data Templates + ContentControl. Simply define the data template for your view model.
<Window.Resources>
<DataTemplate DataType="{x:Type ViewModel:GeneralSettingsViewModel}">
<View:GeneralSettingsView/>
</DataTemplate
<DataTemplate DataType="{x:Type ViewModel:AdvancedSettingsViewModel}">
<View:AdvancedSettingsView/>
</DataTemplate>
</Window.Resources>
What I’m saying here is that GeneralSettingsViewModel should be
rendered using a GeneralSettingsView. That’s exactly what we need !
Because the Views are created using a DataTemplate, we do not need to
setup the DataContext, it will be automatically registered to the
templated object, the ViewModel.
There are two main approaches to your problem:
Inherited windows
Configurable windows
For approach 1, design your window and make the methods overrideable:
In base window xaml, assign the handlers and everything you want:
<Window x:Class="WpfTests.MainWindow"
...
SourceInitialized="Window_SourceInitialized">
In base window, define the handlers as protected virtual (or abstract, if you like to enforce their implementation)
public partial class MainWindow : Window
{
// ...
protected virtual void Window_SourceInitialized(object sender, EventArgs e)
{
}
// ...
}
Create derived windows
public class ExWindow : MainWindow
{
protected override void Window_SourceInitialized(object sender, EventArgs e)
{
// specialized code here
}
}
Change App.xaml to use Startup instead of StartupUri
<Application x:Class="WpfTests.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Startup="Application_Startup">
And manually create your first window, chosing one of the inherited window classes
public partial class App : Application
{
private void Application_Startup(object sender, StartupEventArgs e)
{
var window = new ExWindow();
window.Show();
}
}
The second approach - configurable windows - follows the same principle as a good user control design: The window/control properties are controlled by the creator instead of being controlled by the window/control itself.
So, instead of defining some event handler within the window code, just leave this exercise to the user, who hopefully knows what the window should do:
public partial class MainWindow : Window
{
// I don't care for SourceInitialized (also remove it from XAML)
}
In App.xaml or wherever a window is created:
public partial class App : Application
{
private void Application_Startup(object sender, StartupEventArgs e)
{
var window = new MainWindow();
window.SourceInitialized += window_SourceInitialized;
window.Show();
}
void window_SourceInitialized(object sender, EventArgs e)
{
var window = sender as MainWindow;
// I know how to handle this event for this window instance
}
}
I created a "WPF Application Project" in Visual Studio 2013.
I opened the "MainWindow.xaml" file and I wrote the following XAML code:
<Window x:Class="TestProject.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">
<Window.Resources>
<DataTemplate x:Key="AlphaDataTemplate">
<Label
Name="LabelInDataTemplate"
Content="Good morning!" />
</DataTemplate>
</Window.Resources>
<Grid>
<ContentPresenter
Name="MyContentPresenter"
ContentTemplate="{StaticResource AlphaDataTemplate}" />
<Button
Name="MyButton"
Click="MyButton_OnClick"
Content="Change the content of the Label in the DataTemplate"
Width="320"
Height="30" />
</Grid>
In this XAML file I created a "DataTemplate" which corresponds to the key "AlphaDataTemplate". The DataTmplate contains just one label with the name "LabelInDataTemplate" where I have hardcoded the "Good morning!" string in the "Content" attribute of the label.
Then I use created a "ContentPresenter" with the name "MyContentPresenter" and I pass as content the "DataTemplate" I previously created (AlphaDataTemplate).
As next step, I created a "Button" with the name "MyButton" and I have set a "Click" event called "MyButton_OnClick"
So far so good...!
The question comes now and actually in C# in the code behind file "MainWindow.xaml.cs". See the code below:
using System.Windows;
namespace TestProject
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void MyButton_OnClick(object sender, RoutedEventArgs e)
{
LabelInDataTemplate.Content = "Bye!"; // <-- Tha does not work.
}
}
}
In this C# code behind file you can see the definition of the "Click" (MyButton_OnClick) event of the Button (MyButton) which appears in XAML.
What I am trying to do in this "Click" event, is to change the value of the "Content" of the "Label" (LabelInDataTemplate) which is in the DataTemplate (AlphaDataTemplate).
Unfortunately, that does not work.
I cannot actually access the "Name" (LabelInDataTemplate) of the "Label", because it is contained in the "DataTemplate" (AlphaDataTemplate)
If anyone has any idea, how could I modify from C# the value of an element which is define in a XAML DataTemplate, please give me feedback. I would really appreciate it.
Thank you in advance.
I strongly oppose your method of changing the content of label via DataTemplate, However your requirement is possible, but very subtle.
Code
private void MyButton_OnClick(object sender, RoutedEventArgs e)
{
var alphaDataTemplate = this.Resources["AlphaDataTemplate"] as DataTemplate;
var label = alphaDataTemplate.FindName("LabelInDataTemplate", MyContentPresenter) as Label;
label.Content = "It Works";
}
Please learn MVVM and use proper DataBinding for this purpose. For sake of solving this problem:
Implement INotifyPropertyChanged interface on your Window class and Define string property like below
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public string _contentMsg;
public string ContentMsg
{
get { return _contentMsg; }
set
{
_contentMsg = value;
RaisePropertyChanged("ContentMsg");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propName)
{
if(PropertyChanged !=null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
In your xaml bind the ContentPresenter and update your DataTemplate label like
<ContentPresenter
Name="MyContentPresenter"
Content = "{Binding ContentMsg}"
ContentTemplate="{StaticResource AlphaDataTemplate}" />
<DataTemplate x:Key="AlphaDataTemplate">
<Label
Name="LabelInDataTemplate"
Content="{Binding}" />
Now in click handler (I would use Commands here), set ContentMsg to whatever you want
private void MyButton_OnClick(object sender, RoutedEventArgs e)
{
ContentMsg = "Bye!";
}
Is there a way to create "Instance only" ICommand implementation for Custom Control, WITHOUT static classes "behind"?
I am trying to update previously created custom control.
One of the goals is to ensure multiinstance capability.
If two or more instances of the same custom control was used in the same application, there is (as expected) interferences from any static clases that are used behind.
I figured out how to get rid of the most, but having troubles with ICommand.
Given GUI items on the Custom Control have Command that must be only valid within the User Control instance - instead of this now the Command is interfering with all instances (as example CanExecute makes GUI items active on UserControl Instances where the "local" conditions are not met).
You can create your command and expose it as a property of your ViewModel, then bind to it in your control:
In your ViewModel:
public ICommand MyCommand {get;set;} // construct your command and set it here
in your control:
<Button Command="{Binding MyCommand}"/>
if you are not using MVVM pattern, then you should create the same field in your DataContext (probably in your controls code behind)
you can also use Dependency properties in order to define your command, if you change your command after your user control is created, you should use it.
In general:
In order to know your options when writing in WPF / C# I recommend reading about MVVM pattern, dependency properties, DataContext and Binding - you may know some of this already.
I think you might be confused by the fact that the CanExecute and Execute methods do not have a parameter linking them to the object upon which they are supposed to act.
But remember that the ICommand interface must be implemented by a class, and objects of that class can and should have fields, typically initialized in the constructor.
For example, if you follow the MVVM pattern (as already mentioned by Ron.B.I.), the command typically has a reference to the viewmodel. Or you can use something like a RelayCommand, and capture the viewmodel in a delegate or lambda closure object.
Thank you very much for the answers and clarifications!
You gave me the all deciding kick so I figured it out. I added my examples complete on purpouse.
Following your advices (Ron B I & Dennis) I first wanted read more about the ViewModel.
Under http://msdn.microsoft.com/en-ca/magazine/dd419663.aspx there are examples with non-static class behind. So the solution was simply to add new class in my user control (Exactly as shown on the mentioned site - Figure 3 - some name changed - Copyright belongs to Josh Smith joshsmithonwpf.wordpress.com ):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
namespace WpfCommandControl
{
class CommandImplementation : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
public CommandImplementation(Action<object> execute)
: this(execute, null)
{
}
public CommandImplementation(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion // ICommand Members
}
}
Then in the User Control "Window"
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
namespace WpfCommandControl
{
public partial class CommandControl : UserControl, INotifyPropertyChanged
{
#region [ Private Members ]
private bool _canActivated = false;
private int _counter = 0;
CommandImplementation _activateCommand;
#endregion
#region [ Properties ]
public int CommandCounter
{
get
{
return _counter;
}
set
{
_counter = value;
OnNotifyPropertyChanged("CommandCounter");
}
}
public bool CanActivated
{
get
{
return _canActivated;
}
set
{
_canActivated = value;
OnNotifyPropertyChanged("CanActivated");
}
}
#endregion
#region [ Property_Changed_Utilities ]
public event PropertyChangedEventHandler PropertyChanged;
private void OnNotifyPropertyChanged(String info)
{
// Note: Do not forget to add interface "INotifyPropertyChanged" to your class.
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
# region [ Commands ]
public ICommand ActivateCommand
{
get
{
return _activateCommand;
}
}
#endregion
#region [ Constructor ]
public CommandControl()
{
InitializeComponent();
_activateCommand = new CommandImplementation(param => this.Activate(), param => this.CanActivated);
}
#endregion
#region [ Methods ]
void Activate()
{
CommandCounter++;
}
#endregion
}
}
Most important part:
Command is implemented as Property:
public ICommand ActivateCommand
{
get
{
return _activateCommand;
}
}
So it makes sure it will return the actual instance related Command, that was instantiated with Lambda-Expression in the User Control's constructor:
public CommandControl()
{
InitializeComponent();
_activateCommand = new CommandImplementation(param => this.Activate(), param => this.CanActivated);
}
The Lambda - Expression makes the connection to the logic begind:
param => this.Activate()
For the Activate() function wich will be executed as Command is fired
void Activate()
{
CommandCounter++;
}
And
param => this.CanActivated
For passing the local logic behind for the ICommand CanExecute Property, thus giving you control on when the command can be executed.
In my case I used property that can be bind to the CheckBox, but you can also do it another way...
public bool CanActivated
{
get
{
return _canActivated;
}
set
{
_canActivated = value;
OnNotifyPropertyChanged("CanActivated");
}
}
Again it is as shown from Josh Smith joshsmithonwpf.wordpress.com - I just changed it to instantiate in the constructor instead of check if the private member is null and delivering new instance if needed in the GET part of the Command Property.
Rest of the code is just implementation of needed Properties and OnNotifyPropertyChanged as shown on MSDN.
XAML is simple - just for the proof of concept.
<UserControl x:Class="WpfCommandControl.CommandControl"
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:WpfCommandControl"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<Grid>
<StackPanel>
<CheckBox Content="Activate" IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}, Path=CanActivated}" />
<Button Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=UserControl},
Path=ActivateCommand}"
Content="Click me"
IsEnabled="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=UserControl},
Path=CanActivated}" />
<Label Content="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}, Path=CommandCounter}" IsEnabled="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}, Path=CanActivated}" />
</StackPanel>
</Grid>
As you can see there is only a CheckBox - Binding will provide Enable/Disable of Button.
Click on Button fires the Command that simply increment counter - shown on the Label again trough binding.
Putting all together:
Just one simple XAML Form with four User Controls:
<Window x:Class="CommandsTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CommandsTest"
xmlns:uctrl="clr-namespace:WpfCommandControl;assembly=WpfCommandControl"
Title="MainWindow"
Width="525"
Height="350">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<uctrl:CommandControl Grid.Row="0" Grid.Column="0" />
<uctrl:CommandControl Grid.Row="0" Grid.Column="1" />
<uctrl:CommandControl Grid.Row="1" Grid.Column="0" />
<uctrl:CommandControl Grid.Row="1" Grid.Column="1" />
</Grid>
Firing the commands on every control is exactly as needed inside the element.
All is solved in the WPF way - using Commands and Bindings without any direct interaction with GUI elements, thus the GUI can be exchanged without need of updates in the code behind.
Once again thank you for showing me that there is also another (instance safe) way to implement custom commands in WPF.
I'm trying to make Avalon MVVM compatible in my WPF application. From googling, I found out that AvalonEdit is not MVVM friendly and I need to export the state of AvalonEdit by making a class derived from TextEditor then adding the necessary dependency properties. I'm afraid that I'm quite lost in Herr Grunwald's answer here:
If you really need to export the state of the editor using MVVM, then I suggest you create a class deriving from TextEditor which adds the necessary dependency properties and synchronizes them with the actual properties in AvalonEdit.
Does anyone have an example or have good suggestions on how to achieve this?
Herr Grunwald is talking about wrapping the TextEditor properties with dependency properties, so that you can bind to them. The basic idea is like this (using the CaretOffset property for example):
Modified TextEditor class
public class MvvmTextEditor : TextEditor, INotifyPropertyChanged
{
public static DependencyProperty CaretOffsetProperty =
DependencyProperty.Register("CaretOffset", typeof(int), typeof(MvvmTextEditor),
// binding changed callback: set value of underlying property
new PropertyMetadata((obj, args) =>
{
MvvmTextEditor target = (MvvmTextEditor)obj;
target.CaretOffset = (int)args.NewValue;
})
);
public new string Text
{
get { return base.Text; }
set { base.Text = value; }
}
public new int CaretOffset
{
get { return base.CaretOffset; }
set { base.CaretOffset = value; }
}
public int Length { get { return base.Text.Length; } }
protected override void OnTextChanged(EventArgs e)
{
RaisePropertyChanged("Length");
base.OnTextChanged(e);
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Now that the CaretOffset has been wrapped in a DependencyProperty, you can bind it to a property, say Offset in your View Model. For illustration, bind a Slider control's value to the same View Model property Offset, and see that when you move the Slider, the Avalon editor's cursor position gets updated:
Test XAML
<Window x:Class="AvalonDemo.TestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit"
xmlns:avalonExt="clr-namespace:WpfTest.AvalonExt"
DataContext="{Binding RelativeSource={RelativeSource Self},Path=ViewModel}">
<StackPanel>
<avalonExt:MvvmTextEditor Text="Hello World" CaretOffset="{Binding Offset}" x:Name="editor" />
<Slider Minimum="0" Maximum="{Binding ElementName=editor,Path=Length,Mode=OneWay}"
Value="{Binding Offset}" />
<TextBlock Text="{Binding Path=Offset,StringFormat='Caret Position is {0}'}" />
<TextBlock Text="{Binding Path=Length,ElementName=editor,StringFormat='Length is {0}'}" />
</StackPanel>
</Window>
Test Code-behind
namespace AvalonDemo
{
public partial class TestWindow : Window
{
public AvalonTestModel ViewModel { get; set; }
public TestWindow()
{
ViewModel = new AvalonTestModel();
InitializeComponent();
}
}
}
Test View Model
public class AvalonTestModel : INotifyPropertyChanged
{
private int _offset;
public int Offset
{
get { return _offset; }
set
{
_offset = value;
RaisePropertyChanged("Offset");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
You can use the Document property from the editor and bind it to a property of your ViewModel.
Here is the code for the view :
<Window x:Class="AvalonEditIntegration.UI.View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:AvalonEdit="clr-namespace:ICSharpCode.AvalonEdit;assembly=ICSharpCode.AvalonEdit"
Title="Window1"
WindowStartupLocation="CenterScreen"
Width="500"
Height="500">
<DockPanel>
<Button Content="Show code"
Command="{Binding ShowCode}"
Height="50"
DockPanel.Dock="Bottom" />
<AvalonEdit:TextEditor ShowLineNumbers="True"
Document="{Binding Path=Document}"
FontFamily="Consolas"
FontSize="10pt" />
</DockPanel>
</Window>
And the code for the ViewModel :
namespace AvalonEditIntegration.UI
{
using System.Windows;
using System.Windows.Input;
using ICSharpCode.AvalonEdit.Document;
public class ViewModel
{
public ViewModel()
{
ShowCode = new DelegatingCommand(Show);
Document = new TextDocument();
}
public ICommand ShowCode { get; private set; }
public TextDocument Document { get; set; }
private void Show()
{
MessageBox.Show(Document.Text);
}
}
}
source : blog nawrem.reverse
Not sure if this fits your needs, but I found a way to access all the "important" components of the TextEditor on a ViewModel while having it displayed on a View, still exploring the possibilities though.
What I did was instead of instantiating the TextEditor on the View and then binding the many properties that I will need, I created a Content Control and bound its content to a TextEditor instance that I create in the ViewModel.
View:
<ContentControl Content="{Binding AvalonEditor}" />
ViewModel:
using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Highlighting;
// ...
private TextEditor m_AvalonEditor = new TextEditor();
public TextEditor AvalonEditor => m_AvalonEditor;
Test code in the ViewModel (works!)
// tests with the main component
m_AvalonEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("XML");
m_AvalonEditor.ShowLineNumbers = true;
m_AvalonEditor.Load(#"C:\testfile.xml");
// test with Options
m_AvalonEditor.Options.HighlightCurrentLine = true;
// test with Text Area
m_AvalonEditor.TextArea.Opacity = 0.5;
// test with Document
m_AvalonEditor.Document.Text += "bla";
At the moment I am still deciding exactly what I need my application to configure/do with the textEditor but from these tests it seems I can change any property from it while keeping a MVVM approach.