Is there a way to call external functions from xaml? - wpf

Is there any way to call methods of external objects (for example resource objects) directly from xaml?
I mean something like this:
<Grid xmlns:dm="clr-namespace:MyNameSpace;assembly=MyAssembly">
<Grid.Resources>
<dm:TimeSource x:Key="timesource1"/>
</Grid.Resources>
<Button Click="timesource_updade">Update time</Button>
</Grid>
The method timesource_update is of course the method of the TimeSource object.
I need to use pure XAML, not any code behind.

Check this thread, it has a similar problem. In general you can't call a method directly from xaml.
You could use Commands or you can create an object from xaml which will create a method on a thread, which will dispose itself when it needs.
But I am afraid you can't do it just in pure XAML. In C# you can do everything you can do in XAML, but not other way round. You can only do some certain things from XAML that you can do in C#.

OK, here is the final sollution.
XAML:
<Grid xmlns:dm="clr-namespace:MyNameSpace;assembly=MyAssembly">
<Grid.Resources>
<dm:TimeSource x:Key="timesource1"/>
</Grid.Resources>
<Button Command="{x:Static dm:TimeSource.Update}"
CommandParameter="any_parameter"
CommandTarget="{Binding Source={StaticResource timesource1}}">Update time</Button>
</Grid>
CODE in the TimeSource class:
public class TimeSource : System.Windows.UIElement {
public static RoutedCommand Update = new RoutedCommand();
private void UpdateExecuted(object sender, ExecutedRoutedEventArgs e)
{
// code
}
private void UpdateCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
// Constructor
public TimeSource() {
CommandBinding cb = new CommandBinding(TimeSource.Update, UpdateExecuted, UpdateCanExecute);
CommandBindings.Add(cb2);
}
}
TimeSource has to be derived from UIElement in order to have CommandBindings. But the result is calling outer assembly method directly from XAML. By clicking the button, 'UpdateExecuted' method of the object timesource1 is called and that is exactly what I was looking for.

Related

Move custom event handler from View to ViewModel

I have a custom event named OnVisualChartRangeChanged being fired from a UserControl called HistoricChartControl.
I am using the control in my main application like this:
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<viewModels:HistoricViewModel/>
</Window.DataContext>
<Grid>
<historicChart:HistoricChartControl >
<historicChart:HistoricChartControl
behaviours:ChartBehavior.OnVisualChartRangeChanged="VisualChartRangeChanged"/>
</historicChart:HistoricChartControl>
</Grid>
I want that instead of having the event being handled in the view via the method VisualChartRangeChanged, the event be handled in the ViewModel.
How could I modify my code for this to happen? It would be helpful if you could post specific code as I am new to the WPF way of doing things.
Thanks.
The solution is to use Commands.
Since its a UserControl you may manipulate it to implement ICommandSource interface.
Then your UserControl will be able to bind a Command to ViewModel.
Once the event is being fired you simply call the command which will invoke Execute() method from the ViewModel.
For commanding in WPF I suggest you to read following link:
http://msdn.microsoft.com/en-us/library/ms752308(v=vs.110).aspx
In your ViewModel you will have to offer a property of type ICommand.
EDIT Since you cannot manipulate your UserControl you will have to attach a command on it in XAML.
Interactivity is also an alternative to solve your issue. Take a look at this code:
xmlns:I="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
<ListBox ...>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SelectedItemChangedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
Interactivity is a third party dll from Microsoft Blend.
If you have nuget in visual studio you will be able to find that dll. If not here is the link: http://www.nuget.org/packages/System.Windows.Interactivity.WPF/
This answer is changed once.
Interactivity Solution:
If the used behavior is reusable (you have its source) you can simply move the logic of this behavior to ViewModel level. Follow these 4 steps and it should work if the bindings and DataContext values are correct.
Add reference of both System.Windows.Interactivity and Microsoft.Expression.Interactions to your project:
Create a Command in ViewModel
//ViewModel:
public ICommand VisualChartRangeChangedCommand
{
get { return (ICommand)GetValue(VisualChartRangeChangedCommandProperty); }
set { SetValue(VisualChartRangeChangedCommandProperty, value); }
}
public static readonly DependencyProperty VisualChartRangeChangedCommandProperty =
DependencyProperty.Register("VisualChartRangeChangedCommand", typeof(ICommand), typeof(ViewModel), new UIPropertyMetadata(null));
//In ViewModel constructor:
VisualChartRangeChangedCommand = new ActionCommand(() => doStuff());
override the Behavior and add command ability to it
public class OnVisualChartRangeChangedWithCommand : OnVisualChartRangeChanged<HistoricChartControl>
{
//MyCommand Dependency Property
public ICommand MyCommand
{
get { return (ICommand)GetValue(MyCommandProperty); }
set { SetValue(MyCommandProperty, value); }
}
public static readonly DependencyProperty MyCommandProperty =
DependencyProperty.Register("MyCommand", typeof(ICommand), typeof(OnVisualChartRangeChangedWithCommand), new UIPropertyMetadata(null));
protected override void OnAttached()
{
//replace MouseEnter with other events related to OnVisualChartRangeChanged
AssociatedObject.MouseEnter += _eh;
base.OnAttached();
}
protected override void OnDetaching()
{
AssociatedObject.MouseEnter -= _eh;
base.OnDetaching();
}
void _eh(object sender, MouseEventArgs e)
{
if (MyCommand != null)
MyCommand.Execute(null);
}
}
Link the ViewModel's Command to the overriden Behavior
xmlns:I="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:B="clr-namespace:CoreProject.Behaviors;assembly=CoreProject"
<historicChart:HistoricChartControl>
<I:Interaction.Behaviors>
<B:OnVisualChartRangeChangedWithCommand MyCommand="{Binding VisualChartRangeChangedCommand}"/>
</I:Interaction.Behaviors>
</historicChart:HistoricChartControl>

How to databind Click= to a function on the object and not the page

I'm using Silverlight, but I'd be interested in a WPF answer as well
I have a list that is databound to an linked list of “Favorites”. Each favorite contains a name and a phone number.
The list is bound to a DataTemplate that describes the graphical aspects. In the this template is a button – Dial. When you click on that button I want the Dial() method of the Favorite to be called. Right now the Dial method of the page/window is called.
If this is not possible is there a way I can get the Favorite to somehow be attached to the Button? such that I know which Favorite was associated with the button press?
the below XAML does not work, Text="{Binding Name}" works great as it binds to the Name property on the Favorite, but Click="{Binding Dial}" does not call Dial() on the Favorite.
<DataTemplate x:Key="DataTemplate1">
<StackPanel d:DesignWidth="633" Orientation="Horizontal" Height="93">
<Button x:Name="DialButton" Content="Edit" Click="{Binding Dial}"/>
<TextBlock x:Name="TextBlock" TextWrapping="Wrap" Text="{Binding Name}" FontSize="64" Height="Auto" FontFamily="Segoe WP SemiLight"/>
</StackPanel>
</DataTemplate>
So it should go:
<Button CommandParameter="{Binding}" Command="{Binding Dial}"/>
Then you will receive the data object as the command parameter. In this scenario you must provide a Property that is called Dial and returns an ICommand-implementation. If the property is not available on your data-object but on the main class (code-behind), you must look for it within the binding, use for this the RelativeSource keyword.
Another way is to make a click handler. In the click handler you can cast the sender to a Button (or FrameworkElement) and then get the data object from the DataContext. I assume you tried to create such a solution.
private void Button_Click(object sender, RoutedEventArgs e) {
Button btn = (Button)sender;
MyObject obj = btn.DataContext as MyObject;
if(null != obj){
obj.Dial();
// or Dial(obj);
}
}
The markup must be as follows:
<Button x:Name="DialButton" Content="Edit" Click="Button_Click"/>
The main difference is, that I removed the binding from the Click-Event and registered an event-handler.
A third solution would be, to register a handler in the code behind for the Button.ClickEvent. The principle is similiar as in the second example.
I don't know silverlight very well. Perhaps there are the things a little bit other.
HappyClicker's first solution is the best one for most purposes, since it supports good design patterns such as MVVM.
There is another simple way to get the same result using an attached property, so you can write:
<Button Content="Edit" my:RouteToContext.Click="Edit" />
and the Edit() method will be called on the button's DataContext.
Here is how the RouteToContext class might be implemented:
public class RouteToContext : DependencyObject
{
public static string GetClick(FrameworkElement element) { return (string)element.GetValue(ClickProperty); }
public static void SetClick(FrameworkElement element, string value) { element.SetValue(ClickProperty, value); }
public static DependencyProperty ClickProperty = ConstructEventProperty("Click");
// Additional proprties can be defined here
private static DependencyProperty ConstructEventProperty(string propertyName)
{
return DependencyProperty.RegisterAttached(
propertyName, typeof(string), typeof(RouteToContext),
new PropertyMetadata
{
PropertyChangedCallback = (obj, propertyChangeArgs) =>
obj.GetType().GetEvent(propertyName)
.AddEventHandler(obj, new RoutedEventHandler((sender, eventArgs) =>
((FrameworkElement)sender).DataContext
.GetType().GetMethod((string)propertyChangeArgs.NewValue)
.Invoke(((FrameworkElement)sender).DataContext,
new object[] { sender, eventArgs }
)
))
}
);
}
}
How it works: When the RouteToContext.Click attached property is set, Type.GetEvent() is used to find the event named "Click", and an event handler is added to it. This event handler uses Type.GetMethod() to find the specified method on the DataContext, then invokes the method on the DataContext, passing the same sender and eventArgs it received.

Is there an MVVM-friendly way to use the WebBrowser control in WPF?

Thanks to this question (click me!), I have the Source property of my WebBrowser binding correctly to my ViewModel.
Now I'd like to achieve two more goals:
Get the IsEnabled property of my Back and Forward buttons to correctly bind to the CanGoBack and CanGoForward properties of the WebBrowser.
Figure out how to call the GoForward() and GoBack() methods without resorting to the code-behind and without the ViewModel having to know about the WebBrowser.
I have the following (non-working) XAML markup at the moment:
<WebBrowser
x:Name="_instructionsWebBrowser"
x:FieldModifier="private"
clwm:WebBrowserUtility.AttachedSource="{Binding InstructionsSource}" />
<Button
Style="{StaticResource Button_Style}"
Grid.Column="2"
IsEnabled="{Binding ElementName=_instructionsWebBrowser, Path=CanGoBack}"
Command="{Binding GoBackCommand}"
Content="< Back" />
<Button
Style="{StaticResource Button_Style}"
Grid.Column="4"
IsEnabled="{Binding ElementName=_instructionsWebBrowser, Path=CanGoForward}"
Command="{Binding GoForwardCommand}"
Content="Forward >" />
I'm pretty sure the problem is that CanGoBack and CanGoForward are not dependency properties (and don't implement INotifyChanged), but I'm not quite sure how to get around that.
Questions:
Is there any way to hook up attached properties (as I did with Source) or something similar to get the CanGoBack and CanGoForward bindings to work?
How do write the GoBackCommand and GoForwardCommand so they are independent of the code-behind and ViewModel and can be declared in markup?
For anyone who comes across this question and wants a complete solution, here it is. It combines all of the suggestions made in this thread and the linked threads (and others those link to).
XAML:
http://pastebin.com/aED9pvW8
C# class:
http://pastebin.com/n6cW9ZBB
Example XAML usage:
http://pastebin.com/JpuNrFq8
Note: The example assumes your view binds to a ViewModel that provides the source URL to the browser. A very rudimentary navigation bar with back, forward, and refresh buttons and address bar is provided just for demonstration.
Enjoy. I have set the expiration on those pastebin's to never, so they should be available for as long as pastebin exists.
I used this in my bindable webbrowser wrapper:
CommandBindings.Add(new CommandBinding(NavigationCommands.BrowseBack, BrowseBack, CanBrowseBack));
CommandBindings.Add(new CommandBinding(NavigationCommands.BrowseForward, BrowseForward, CanBrowseForward));
CommandBindings.Add(new CommandBinding(NavigationCommands.BrowseHome, GoHome, TrueCanExecute));
CommandBindings.Add(new CommandBinding(NavigationCommands.Refresh, Refresh, TrueCanExecute));
CommandBindings.Add(new CommandBinding(NavigationCommands.BrowseStop, Stop, TrueCanExecute));
Note that I created my bindable webbrowser as FrameworkElement that exposes DependencyProperties and calls methods on the actual browser element, so i can set CommandBindings on it.
That way, you can use the default NavigationCommands in your View.
The used handlers are:
private void CanBrowseBack(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = webBrowser.CanGoBack;
}
private void BrowseBack(object sender, ExecutedRoutedEventArgs e) {
webBrowser.GoBack();
}
private void CanBrowseForward(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = webBrowser.CanGoForward;
}
private void BrowseForward(object sender, ExecutedRoutedEventArgs e) {
webBrowser.GoForward();
}
private void TrueCanExecute(object sender, CanExecuteRoutedEventArgs e) { e.CanExecute = true; }
private void Refresh(object sender, ExecutedRoutedEventArgs e) {
try { webBrowser.Refresh(); }
catch (Exception ex) { PmsLog.LogException(ex, true); }
}
private void Stop(object sender, ExecutedRoutedEventArgs e) {
mshtml.IHTMLDocument2 doc = WebBrowser.Document as mshtml.IHTMLDocument2;
if (doc != null)
doc.execCommand("Stop", true, null);
}
private void GoHome(object sender, ExecutedRoutedEventArgs e) {
Source = new Uri(Home);
}
Your question seems to imply that in order to correctly implement an MVVM pattern you are not allowed to have any code-behind. But perhaps adding some code-behind to your view will make it much easier to hook it up with your view-model. You can add dependency properties to the view and let it listen for INotifyPropertyChanged events.

How to bind Close command to a button

The easiest way is to implement ButtonClick event handler and invoke Window.Close() method, but how doing this through a Command binding?
All it takes is a bit of XAML...
<Window x:Class="WCSamples.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Close"
Executed="CloseCommandHandler"/>
</Window.CommandBindings>
<StackPanel Name="MainStackPanel">
<Button Command="ApplicationCommands.Close"
Content="Close Window" />
</StackPanel>
</Window>
And a bit of C#...
private void CloseCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
this.Close();
}
(adapted from this MSDN article)
Actually, it is possible without C# code. The key is to use interactions:
<Button Content="Close">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:CallMethodAction TargetObject="{Binding ElementName=window}" MethodName="Close"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
In order for this to work, just set the x:Name of your window to "window", and add these two namespaces:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
This requires that you add the Expression Blend SDK DLL to your project, specifically Microsoft.Expression.Interactions.
In case you don't have Blend, the SDK can be downloaded here.
I think that in real world scenarios a simple click handler is probably better than over-complicated command-based systems but you can do something like that:
using RelayCommand from this article http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
public class MyCommands
{
public static readonly ICommand CloseCommand =
new RelayCommand( o => ((Window)o).Close() );
}
<Button Content="Close Window"
Command="{X:Static local:MyCommands.CloseCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}}"/>
If the window was shown with Window.ShowDialog():
The simplest solution that I know of is to set the IsCancel property to true of the close Button:
<Button Content="Close" IsCancel="True" />
No bindings needed, WPF will do that for you automatically!
This properties provide an easy way of saying these are the "OK" and "Cancel" buttons on a dialog. It also binds the ESC key to the button.
Reference: MSDN Button.IsCancel property.
For .NET 4.5 SystemCommands class will do the trick (.NET 4.0 users can use WPF Shell Extension google - Microsoft.Windows.Shell or Nicholas Solution).
<Window.CommandBindings>
<CommandBinding Command="{x:Static SystemCommands.CloseWindowCommand}"
CanExecute="CloseWindow_CanExec"
Executed="CloseWindow_Exec" />
</Window.CommandBindings>
<!-- Binding Close Command to the button control -->
<Button ToolTip="Close Window" Content="Close" Command="{x:Static SystemCommands.CloseWindowCommand}"/>
In the Code Behind you can implement the handlers like this:
private void CloseWindow_CanExec(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
private void CloseWindow_Exec(object sender, ExecutedRoutedEventArgs e)
{
SystemCommands.CloseWindow(this);
}
In the beginning I was also having a bit of trouble figuring out how this works so I wanted to post a better explanation of what is actually going on.
According to my research the best way to handle things like this is using the Command Bindings. What happens is a "Message" is broadcast to everything in the program. So what you have to do is use the CommandBinding. What this essentially does is say "When you hear this Message do this".
So in the Question the User is trying to Close the Window. The first thing we need to do is setup our Functions that will be called when the SystemCommand.CloseWindowCommand is broadcast. Optionally you can assign a Function that determines if the Command should be executed. An example would be closing a Form and checking if the User has saved.
MainWindow.xaml.cs (Or other Code-Behind)
void CloseApp( object target, ExecutedRoutedEventArgs e ) {
/*** Code to check for State before Closing ***/
this.Close();
}
void CloseAppCanExecute( object sender, CanExecuteRoutedEventArgs e ) {
/*** Logic to Determine if it is safe to Close the Window ***/
e.CanExecute = true;
}
Now we need to setup the "Connection" between the SystemCommands.CloseWindowCommand and the CloseApp and CloseAppCanExecute
MainWindow.xaml (Or anything that implements CommandBindings)
<Window.CommandBindings>
<CommandBinding Command="SystemCommands.CloseWindowCommand"
Executed="CloseApp"
CanExecute="CloseAppCanExecute"/>
</Window.CommandBindings>
You can omit the CanExecute if you know that the Command should be able to always be executed Save might be a good example depending on the Application. Here is a Example:
<Window.CommandBindings>
<CommandBinding Command="SystemCommands.CloseWindowCommand"
Executed="CloseApp"/>
</Window.CommandBindings>
Finally you need to tell the UIElement to send out the CloseWindowCommand.
<Button Command="SystemCommands.CloseWindowCommand">
Its actually a very simple thing to do, just setup the link between the Command and the actual Function to Execute then tell the Control to send out the Command to the rest of your program saying "Ok everyone run your Functions for the Command CloseWindowCommand".
This is actually a very nice way of handing this because, you can reuse the Executed Function all over without having a wrapper like you would with say WinForms (using a ClickEvent and calling a function within the Event Function) like:
protected override void OnClick(EventArgs e){
/*** Function to Execute ***/
}
In WPF you attach the Function to a Command and tell the UIElement to execute the Function attached to the Command instead.
I hope this clears things up...
One option that I've found to work is to set this function up as a Behavior.
The Behavior:
public class WindowCloseBehavior : Behavior<Window>
{
public bool Close
{
get { return (bool) GetValue(CloseTriggerProperty); }
set { SetValue(CloseTriggerProperty, value); }
}
public static readonly DependencyProperty CloseTriggerProperty =
DependencyProperty.Register("Close", typeof(bool), typeof(WindowCloseBehavior),
new PropertyMetadata(false, OnCloseTriggerChanged));
private static void OnCloseTriggerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var behavior = d as WindowCloseBehavior;
if (behavior != null)
{
behavior.OnCloseTriggerChanged();
}
}
private void OnCloseTriggerChanged()
{
// when closetrigger is true, close the window
if (this.Close)
{
this.AssociatedObject.Close();
}
}
}
On the XAML Window, you set up a reference to it and bind the Behavior's Close property to a Boolean "Close" property on your ViewModel:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
<i:Interaction.Behaviors>
<behavior:WindowCloseBehavior Close="{Binding Close}" />
</i:Interaction.Behaviors>
So, from the View assign an ICommand to change the Close property on the ViewModel which is bound to the Behavior's Close property. When the PropertyChanged event is fired the Behavior fires the OnCloseTriggerChanged event and closes the AssociatedObject... which is the Window.

Need help handling events of a DataTemplate in the Application.xaml file

I have in my application a data template that has a few buttons.
I want those buttons' even handler to be fired in the current page (I am using this template in many pages) rather than in the Application.xaml.vb/cs file, since I want different actions on each page.
I hope I am clear.
You can use commanding to achieve this. Have the Buttons in the DataTemplate execute specific Commands:
<Button Command="{x:Static MyCommands.SomeCommand}"/>
Then have each view that uses that DataTemplate handle the Command:
<UserControl>
<UserCommand.CommandBindings>
<CommandBinding Command="{x:Static MyCommands.SomeCommand}"
Executed="_someHandler"/>
</UserCommand.CommandBindings>
</UserControl>
EDIT after comments: Once you have created a code-behind for your ResourceDictionary as per these instructions, you can simply connect events in the usual fashion:
In MyResources.xaml:
<ListBox x:Key="myListBoxResource" ItemSelected="_listBox_ItemSelected"/>
Then in MyResources.xaml.cs:
private void _listBox_ItemSelected(object sender, EventArgs e)
{
...
}
If you use events and not commands, then in your Click event handler just write
private void Button_Click(object sender, RoutedEventArgs e)
{
var dataItem = (FrameworkElement)sender).DataContext;
// process dataItem
}

Resources