|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Services
Chapters
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
Preface And ThanksI am a .NET programmer, but a busy one, I do VB .NET and C#, ASP .NET / Winforms / WPF / WCF Flash Silverlight the lot. Basically I keep my toe in. But when I started writing this article series I naturally chose my favourite language (which happens to be C#). I since got an email from an individual who requested that I publish this series with source code in VB .NET and C#. I simply stated I didn't have time. So this individual (Robert Ranck) volunteered to help out, and do the translation to VB .NET, based on my orginal C# projects So for that and the subsequent VB .NET projects that you will find here I ask you to thank Robert Ranck. Cheers Robert, your contributions will surely make this series more open to all .NET developers. IntroductionThis article is the 3rd in my series of beginners articles for WPF. In this article we will discuss 2 important parts of WPF development,
In this article I'm aiming to cover is a brief introduction into the following:
RoutedEventsWhat they areRouted events are a new concept to most developers. In good old fashioned .NET 1.x/2.0 we all probably used some custom events or hooked some delegates onto some existing event such asprivate System.Web.Forms.Button button1;
button1.click+=new EventHandler(button1_Click);
...
private void button1_Click(object sender, EventArge e)
{
//Click Event seen, so something about it
}
This is all well and good right. The System.Web.Forms.Button exposes an OnClick event, which we subscribe to using a standard EventHandler delegate, and we receive the event when the System.Web.Forms.Button raises its internal OnClick event. From here on in, this type of event subscription/notification will be referred to as CLR events. In WPF things are a little different. There are 3 types of event notification methods, namely
What actually happens is that when an event is raised in travels up and down the VisualTree invoking any handlers that are subscribed to the RoutedEvent. This travesal of the VisualTree, will not be the entire tree, but rather the portion of the tree that is directly related to the element that raised the event. Josh smith has an excellent blog entry about this, which you can read right here, should you prefer to look at other resources. It is fairly common for one logical event to be represented by 2 actual events, one Tunnelling and one Bubbling. There is a naming convention in place to aid in determining how these events were created. Tunnelling events usually start with the "PreviewXXX" and Bubbling events are usually just XXX. For example PreviewKeyDown (Tunnelling) and KeyDown (Bubbling). How they workIn order to understand them further I have provided as part of the overall demo solution (at the top of this article) a project entitled "Part3_RoutedEventViewer" which is a standard WPF Application, that when run can be used to examine WPF commands. When the application is launched is should resemble something like the following.
This little demo application, should give you a better understanding of how routed events work. But before I show you a few more screen shots, lets just familiarise ourselves with the applications XAML code, as this is a faily important before I can discuss the routed events, that you'll see. <Window x:Class="Part3_RoutedEventViewer.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Examining Routed Events" Height="300" Width="300"
WindowState="Maximized">
<Grid x:Name="gridMain">
<Grid.Resources>
//Ommiited for clarity
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="100*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<Button x:Name="btnTop" Margin="10" Padding="2"
Content="Examining Routed Events" Height="auto"/>
<Button x:Name="btnClearItems" Margin="10" Padding="2"
Content="Clear Items" Height="auto" Click="btnClearItems_Click"/>
</StackPanel>
<ListView x:Name="lvResults" Margin="0,0,0,0"
IsSynchronizedWithCurrentItem="True" Grid.Row="2" >
<ListView.View>
<GridView ColumnHeaderContainerStyle="{StaticResource headerContainerStyle}" >
<GridViewColumn Header="RoutedEventName" Width="150"
CellTemplate="{StaticResource RoutedEventNameTemplate}"/>
<GridViewColumn Header="SenderName" Width="100"
CellTemplate="{StaticResource SenderNameTemplate}"/>
<GridViewColumn Header="ArgsSource" Width="100"
CellTemplate="{StaticResource ArgsSourceTemplate}"/>
<GridViewColumn Header="OriginalSource" Width="100"
CellTemplate="{StaticResource OriginalSourceTemplate}"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
And the C# code behind is very simple, it basically just subscribes to a whole bunch of Tunnelling and Bubbling 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;
namespace Part3_RoutedEventViewer
{
/// <summary>
/// Demo application that displays some data about the events
/// that were recieved by a users actions. Which allows users
/// to see the difference between tunneling/routed events
/// </summary>
public partial class Window1 : Window
{
#region Ctor
/// <summary>
/// Wires up several of the standard <see cref="FrameworkElement">
/// FrameworkElement</see> Tunneling/Bubbling <see cref="RoutedEvent">RoutedEvents</see>.
/// This demo also displays some data about the events that were recieved by
/// a users actions.
/// </summary>
public Window1()
{
InitializeComponent();
UIElement[] els = { this, gridMain, btnTop, lvResults };
foreach (UIElement el in els)
{
//keyboard
el.PreviewKeyDown += GenericHandler;
el.PreviewKeyUp += GenericHandler;
el.PreviewTextInput += GenericHandler;
el.KeyDown += GenericHandler;
el.KeyUp += GenericHandler;
el.TextInput += GenericHandler;
//Mouse
el.MouseDown += GenericHandler;
el.MouseUp += GenericHandler;
el.PreviewMouseDown += GenericHandler;
el.PreviewMouseUp += GenericHandler;
//Stylus
el.StylusDown += GenericHandler;
el.StylusUp += GenericHandler;
el.PreviewStylusDown += GenericHandler;
el.PreviewStylusUp += GenericHandler;
el.AddHandler(Button.ClickEvent, new RoutedEventHandler(GenericHandler));
}
}
#endregion
#region Private Methods
/// <summary>
/// Creates a new <see cref="EventDemoClass">EventDemoClass</see>
/// to represent the <see cref="RoutedEvent">RoutedEvent</see>.
/// And adds this new EventDemoClass to the listbox
/// </summary>
private void GenericHandler(object sender, RoutedEventArgs e)
{
lvResults.Items.Add(new EventDemoClass()
{
RoutedEventName = e.RoutedEvent.Name,
SenderName = typeWithoutNamespace(sender),
ArgsSource = typeWithoutNamespace(e.Source),
OriginalSource = typeWithoutNamespace(e.OriginalSource)
});
}
/// <summary>
/// Returns the type name without the namespace portion
/// </summary>
private string typeWithoutNamespace(object obj)
{
string[] astr = obj.GetType().ToString().Split('.');
return astr[astr.Length - 1];
}
/// <summary>
/// Clears the listbox of events
/// </summary>
private void btnClearItems_Click(object sender, RoutedEventArgs e)
{
lvResults.Items.Clear();
}
#endregion
}
#region EventDemoClass CLASS
/// <summary>
/// A simpy data class that is used to display event data
/// </summary>
public class EventDemoClass
{
public string RoutedEventName { get; set; }
public string SenderName { get; set; }
public string ArgsSource { get; set; }
public string OriginalSource { get; set; }
}
#endregion
}
And heres the VB .NET version Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Data
Imports System.Windows.Documents
Imports System.Windows.Input
Imports System.Windows.Media
Imports System.Windows.Media.Imaging
Imports System.Windows.Navigation
Imports System.Windows.Shapes
''' <summary>
''' Demo application that displays some data about the events
''' that were recieved by a users actions. Which allows users
''' to see the difference between tunneling/routed events
''' </summary>
Partial Public Class Window1
Inherits Window
#Region "Ctor"
''' <summary>
''' Wires up several of the standard <see cref="FrameworkElement">
''' FrameworkElement</see> Tunneling/Bubbling <see cref="RoutedEvent">RoutedEvents</see>.
''' This demo also displays some data about the events that were recieved by
''' a users actions.
''' </summary>
Public Sub New()
InitializeComponent()
Dim els As UIElement() = {Me, gridMain, btnTop, lvResults}
For Each el As UIElement In els
'key board
AddHandler el.PreviewKeyDown, AddressOf GenericHandler
AddHandler el.PreviewKeyUp, AddressOf GenericHandler
AddHandler el.PreviewTextInput, AddressOf GenericHandler
AddHandler el.KeyDown, AddressOf GenericHandler
AddHandler el.KeyUp, AddressOf GenericHandler
AddHandler el.TextInput, AddressOf GenericHandler
'mouse
AddHandler el.MouseDown, AddressOf GenericHandler
AddHandler el.MouseUp, AddressOf GenericHandler
AddHandler el.PreviewMouseDown, AddressOf GenericHandler
AddHandler el.PreviewMouseUp, AddressOf GenericHandler
'stylus
AddHandler el.StylusDown, AddressOf GenericHandler
AddHandler el.StylusUp, AddressOf GenericHandler
AddHandler el.PreviewStylusDown, AddressOf GenericHandler
AddHandler el.PreviewStylusUp, AddressOf GenericHandler
el.AddHandler(Button.ClickEvent, New RoutedEventHandler(AddressOf GenericHandler))
Next
End Sub
#End Region
#Region "Private Methods"
''' <summary>
''' Creates a new <see cref="EventDemoClass">EventDemoClass</see>
''' to represent the <see cref="RoutedEvent">RoutedEvent</see>.
''' And adds this new EventDemoClass to the listbox
''' </summary>
Private Sub GenericHandler(ByVal sender As Object, ByVal e As RoutedEventArgs)
Dim eventClass As New EventDemoClass()
eventClass.RoutedEventName = e.RoutedEvent.Name
eventClass.SenderName = typeWithoutNamespace(sender)
eventClass.ArgsSource = typeWithoutNamespace(e.Source)
eventClass.OriginalSource = typeWithoutNamespace(e.OriginalSource)
lvResults.Items.Add(eventClass)
End Sub
''' <summary>
''' Returns the type name without the namespace portion
''' </summary>
Private Function typeWithoutNamespace(ByVal obj As Object) As String
Dim astr As String() = obj.GetType().ToString().Split(".")
Return astr(astr.Length - 1)
End Function
''' <summary>
''' Clears the listbox of events
''' </summary>
Private Sub btnClearItems_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
lvResults.Items.Clear()
End Sub
#End Region
End Class
#Region "EventDemoClass CLASS"
''' <summary>
''' A simpy data class that is used to display event data
''' </summary>
Public Class EventDemoClass
#Region "Instance Fields"
Private newRoutedEventName As String
Private newSenderName As String
Private newArgsSource As String
Private newOriginalSource As String
#End Region
#Region "Propeties"
Public Property RoutedEventName() As String
Get
Return newRoutedEventName
End Get
Set(ByVal value As String)
newRoutedEventName = value
End Set
End Property
Public Property SenderName() As String
Get
Return newSenderName
End Get
Set(ByVal value As String)
newSenderName = value
End Set
End Property
Public Property ArgsSource() As String
Get
Return newArgsSource
End Get
Set(ByVal value As String)
newArgsSource = value
End Set
End Property
Public Property OriginalSource() As String
Get
Return newOriginalSource
End Get
Set(ByVal value As String)
newOriginalSource = value
End Set
End Property
#End Region
End Class
#End Region
It can be seen that this
So with this in mind, let me now show you some example screen shots that were generated using this demo project "Part3_RoutedEventViewer". If we click the Window, we can observe the following events are seen. We only see Window level events this is due to
But if we click the actual button (on the left), we can observe the following events are seen, as the Button is a sibling of the Grid who in tun is a sibling of the Window1 I hope this helps you better understand how RoutedEvents traverse the Visual Tree.
How to consume themConsumingRoutedEvents is the same as it always was, we can do it in XAML like
<Button x:Name="btnClearItems" Content="Clear Items" Click="btnClearItems_Click"/>
Of course you need to have a section of code entitled "btnClearItems_Click" in the code behind, so that the event handler has an actual delegate to a real method. Alternatively you can simply subscribe to a Button btn = new Button();
btn.Click += new RoutedEventHandler(btn_Click);
....
....
....
void btn_Click(object sender, RoutedEventArgs e)
{
//seen event do something
}
And the VB .NET version btn.AddHandler(Button.ClickEvent, New RoutedEventHandler(AddressOf btn_Click))
.....
.....
.....
Private Sub GenericHandler(ByVal sender As Object, ByVal e As RoutedEventArgs)
//seen event do something
End Sub
Or even like this (if you like anonomous delegates. ) Button btn = new Button();
btn.Click += delegate(object sender, RoutedEventArgs e)
{
//seen event do something
};
And we can even add directly to the handlers of a UI Element, such as Button btn = new Button();
btn.AddHandler(Button.ClickEvent, new RoutedEventHandler(GenericHandler));
Or in VB .NET btn.AddHandler(Button.ClickEvent, New RoutedEventHandler(AddressOf GenericHandler))
How to create themI have attached another demo project (entitled "Part3_RoutedEventsExample") which is part of the overall solution at the top of this article. Within this project there are 2 things covered.
When this application is run, it looks like the following figure
Where there is a single Firstly using standard RoutedEventArgs //The actual event routing
public static readonly RoutedEvent CustomClickEvent =
EventManager.RegisterRoutedEvent(
"CustomClick", RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(UserControlThatCreatesEvent));
And here is the VB .NET version Public Shared ReadOnly CustomClickEvent As RoutedEvent =
EventManager.RegisterRoutedEvent("CustomClick",
RoutingStrategy.Bubble, GetType(RoutedEventHandler),
GetType(UserControlThatCreatesEvent))
What is important, is that in //add remove handlers
public event RoutedEventHandler CustomClick
{
add { AddHandler(CustomClickEvent, value); }
remove { RemoveHandler(CustomClickEvent, value); }
}
And here is the VB .NET version 'using standard event args
Public Custom Event CustomClick As RoutedEventHandler
AddHandler(ByVal value As RoutedEventHandler)
Me.AddHandler(CustomClickEvent, value)
End AddHandler
RemoveHandler(ByVal value As RoutedEventHandler)
Me.RemoveHandler(CustomClickEvent, value)
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As RoutedEventArgs)
Me.RaiseEvent(e)
End RaiseEvent
End Event
And finally we must raise the event, this is achieved as follows (VB code will be different so please see the attached project) : //raise our custom CustomClickEvent event
RoutedEventArgs args = new RoutedEventArgs(CustomClickEvent);
RaiseEvent(args);
And here is the VB .NET version Dim args As New RoutedEventArgs(CustomClickEvent)
MyBase.RaiseEvent(args)
It can be seen that we use the Second using custom RoutedEventArgs //The actual event routing
public static readonly RoutedEvent CustomClickWithCustomArgsEvent =
EventManager.RegisterRoutedEvent(
"CustomClickWithCustomArgs", RoutingStrategy.Bubble,
typeof(CustomClickWithCustomArgsEventHandler),
typeof(UserControlThatCreatesEvent));
And here is the VB .NET version Public Shared ReadOnly CustomClickWithCustomArgsEvent
As RoutedEvent = EventManager.RegisterRoutedEvent
("CustomClickWithCustomArgs", RoutingStrategy.Bubble,
GetType(CustomClickWithCustomArgsEventHandler),
GetType(UserControlThatCreatesEvent))
Whats important this time is that we specify a new delegate type for the event handler, in this case, this is public delegate void CustomClickWithCustomArgsEventHandler(object sender, CustomEventArgs e);
And here is the VB .NET version Public Delegate Sub CustomClickWithCustomArgsEventHandler(ByVal sender As Object, ByVal e As CustomEventArgs)
And as before we must declare the event handler section, such that subscribers to the //add remove handlers
public event CustomClickWithCustomArgsEventHandler CustomClickWithCustomArgs
{
add { AddHandler(CustomClickWithCustomArgsEvent, value); }
remove { RemoveHandler(CustomClickWithCustomArgsEvent, value); }
}
And here is the VB .NET version 'using custom event args
Public Custom Event CustomClickWithCustomArgs As CustomClickWithCustomArgsEventHandler
AddHandler(ByVal value As CustomClickWithCustomArgsEventHandler)
Me.AddHandler(CustomClickWithCustomArgsEvent, value)
End AddHandler
RemoveHandler(ByVal value As CustomClickWithCustomArgsEventHandler)
Me.RemoveHandler(CustomClickWithCustomArgsEvent, value)
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As CustomEventArgs)
Me.RaiseEvent(e)
End RaiseEvent
End Event
And finally we must raise the event using our custom RoutedEventArgs. This is achieved as follows (VB code will be different so please see the attached project) : //raise our custom CustomClickWithCustomArgs event
CustomEventArgs args = new CustomEventArgs(CustomClickWithCustomArgsEvent, ++clickedCount);
RaiseEvent(args);
Where we are using these custom /// <summary>
/// CustomEventArgs : a custom event argument class
/// which simply holds an int value representing
/// how many times the associated event has been fired
/// </summary>
public class CustomEventArgs : RoutedEventArgs
{
#region Instance fields
public int SomeNumber { get; private set; }
#endregion
#region Ctor
/// <summary>
/// Constructs a new CustomEventArgs object
/// using the parameters provided
/// </summary>
/// <param name="someNumber">the value for the events args</param>
public CustomEventArgs(RoutedEvent routedEvent,
int someNumber)
: base(routedEvent)
{
this.SomeNumber = someNumber;
}
#endregion
}
And here is the VB .NET versin 'Me.RaiseCustomClickWithCustomArgsEvent()
'raise our custom CustomClickWithCustomArgs event
ClickedCount = ClickedCount + 1
Dim args As New CustomEventArgs(CustomClickWithCustomArgsEvent, ClickedCount)
MyBase.RaiseEvent(args)
Where we are using these custom Imports System
Imports System.Windows
''' <summary>
''' CustomEventArgs : a custom event argument class
''' which simply holds an int value representing
''' how many times the associated event has been fired
''' </summary>
Public Class CustomEventArgs
Inherits System.Windows.RoutedEventArgs
#Region "Instance Fields"
Private newSomeNumber As Integer
#End Region
#Region "Properties"
Public Property SomeNumber() As Integer
Get
Return newSomeNumber
End Get
Set(ByVal value As Integer)
newSomeNumber = value
End Set
End Property
#End Region
#Region "Ctor"
''' <summary>
''' Constructs a new CustomEventArgs object
'''using the parameters provided
'''</summary>
'''<param name="someNumber">the value for the events args</param>
Public Sub New(ByVal routedEvent As System.Windows.RoutedEvent, ByVal someNumber As Integer)
MyBase.New(routedEvent)
Me.SomeNumber = someNumber
End Sub
#End Region
End Class
And that concludes my brief introduction/rant on RoutedEvents. I hope it gave you an inkling as to how they work. RoutedCommandsWhat Are Commands
Using these inbuilt Commands, it is possible to construct some pretty elaborate functionality, with absolutely no procedural code at all. For example lets have a look at one of the projects supplied within the overall solution at the top of this article. This is a small project entitled "Part3_Using_Built_In_Commands" which simply uses the inbuilt EditingCommands to create a small text editor with Cut/Paste/Copy/Undo/Redo functionality. Here is the code, this is all of it, its just XAML. <Window x:Class="Part3_Using_Built_In_Commands.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Simple use of Built In ApplicationCommands" Height="500" Width="500"
ResizeMode="NoResize"
WindowStartupLocation="CenterScreen">
<StackPanel Orientation="Vertical" Width="auto">
<StackPanel Orientation="Horizontal" Background="Gainsboro" Margin="10" Height="40">
<Button Command="ApplicationCommands.Cut" CommandTarget="{Binding ElementName=textBox}"
Margin="5,5,5,5" Content ="ApplicationCommands.Cut"/>
<Button Command="Copy" CommandTarget="{Binding ElementName=textBox}"
Margin="5,5,5,5" Content="ApplicationCommands.Copy"/>
<Button Command="Paste" CommandTarget="{Binding ElementName=textBox}"
Margin="5,5,5,5" Content="ApplicationCommands.Paste"/>
<Button Command="Undo" CommandTarget="{Binding ElementName=textBox}"
Margin="5,5,5,5" Content="ApplicationCommands.Undo"/>
<Button Command="Redo" CommandTarget="{Binding ElementName=textBox}"
Margin="5,5,5,5" Content="ApplicationCommands.Redo"/>
</StackPanel>
<TextBlock HorizontalAlignment="Left" Margin="5,5,5,5"
Text="This window demonstrates built in commands (standard ones),
with no procedual code at all......that's pretty neat I think.
Type into the text box and use the buttons provided to see
what it does" TextWrapping="Wrap" Height="auto"/>
<Label Content="Type in the textbox, maybe try selecting some text...
Watch the buttons become enabled"/>
<TextBox x:Name="textBox" HorizontalAlignment="Left" Margin="5,5,5,5" MaxLines="60" Height="300" Width="470"
Background="#FFF1FFB2"/>
</StackPanel>
</Window>
See how all we need to do is on the
Well thats pretty cool right? I think so. But what about if we want to create our own commands. How do we go about doing that what plumbing do we need to do. Well it kind of goes like this.
As previously stated Commands also build apon the Routed strategy that we saw with Ok, so as I've just stated there are several steps required in creating and using your own
As can be seen from this diagram, there are 3 different classes involved. So lets have a look at some code for each of these 3 classes now shall we. Step1 : Declare a RoutedCommand The first step is to define a using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input; //for the
namespace Part3_Using_Our_Own_Commands
{
/// <summary>
/// Declare a new <see cref="RoutedCommand">RoutedCommand</see> that
/// is used by the <see cref="Window1">Window1</see> class where the
/// Command bindings and Command Sink events are declared. The Actual
/// Comman is used on a Button within the <see cref="UserControlThatUsesCustomCommand">
/// UserControlThatUsesCustomCommand</see> UserControl
/// </summary>
public class CustomCommands
{
#region Instance Fields
public static readonly RoutedCommand simpleCommand =
new RoutedCommand("simpleCommand",typeof(CustomCommands));
#endregion
}
}
And in VB .NET Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Windows.Input
''' <summary>
''' Declare a new <see cref="RoutedCommand">RoutedCommand</see> that
''' is used by the <see cref="Window1">Window1</see> class where the
''' Command bindings and Command Sink events are declared. The Actual
''' Comman is used on a Button within the <see cref="UserControlThatUsesCustomCommand">
''' UserControlThatUsesCustomCommand</see> UserControl
''' </summary>
Public Class CustomCommands
Public Sub New()
End Sub
#Region "Instance Fields"
Public Shared ReadOnly simpleCommand
As New RoutedCommand("simpleCommand",
GetType(CustomCommands))
#End Region
End Class
Step2 : Create a CommandBinding, for where the RoutedCommand sinks are used A <Window.CommandBindings>
<CommandBinding Command="{x:Static local:CustomCommands.simpleCommand}"
CanExecute="simpleCommand_CanExecute"
Executed="simpleCommand_Executed"
/>
</Window.CommandBindings>
This private void simpleCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = !(string.IsNullOrEmpty(txtCantBeEmpty.Text));
}
private void simpleCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show(txtCantBeEmpty.Text);
}
And in VB .NET we have the following Private Sub simpleCommand_CanExecute(ByVal sender As Object, ByVal e As CanExecuteRoutedEventArgs)
e.CanExecute = Not (String.IsNullOrEmpty(txtCantBeEmpty.Text))
End Sub
Private Sub simpleCommand_Executed(ByVal sender As Object, ByVal e As ExecutedRoutedEventArgs)
MessageBox.Show(txtCantBeEmpty.Text)
End Sub
These 2 events are enough to enable/disable the <UserControl x:Class="Part3_Using_Our_Own_Commands.UserControlThatUsesCustomCommand"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Part3_Using_Our_Own_Commands"
Height="auto" Width="auto">
<Button Margin="5,5,5,5" Command="{x:Static local:CustomCommands.simpleCommand}"
Content="Click me if you can"/>
</UserControl>
So this And thats how Automation PeersRecall earlier in our discussion about But now that you know that a So in this case, If we used the of a Namespaces
The I have changed Joshs code slightly, to use the Here is a code snippet to illustrate how to programatically click a button using the automation peers AutomationPeer peer = UIElementAutomationPeer.CreatePeerForElement(start);
IInvokeProvider invokeProv = peer.GetPattern(PatternInterface.Invoke)
as IInvokeProvider;
invokeProv.Invoke();
I did say it was fairly obscur. For example what is this GetPattern thing all about. Well like I also said there are many automation peers, all of which relate to different UI elements, so the | ||||||||||||||||||||