Content update - Preview events (user story 1878475) (#1332)

* Add article, toc, and redirects

* Add a prerequisites section to 3 other event articles

* Further edits

* Update dotnet-desktop-guide/net/wpf/toc.yml

Co-authored-by: Andy (Steve) De George <[email protected]>

* Update article links

* Minor clarification

Co-authored-by: Andy (Steve) De George <[email protected]>
This commit is contained in:
Tris Shores
2022-03-14 15:26:19 -07:00
committed by GitHub
co-authored by Andy De George
parent 084c3e0ba6
commit dd0052ecec
21 changed files with 616 additions and 2 deletions
+8
View File
@@ -628,6 +628,14 @@
{ {
"source_path": "dotnet-desktop-guide/framework/wpf/events/visual-basic-and-wpf-event-handling.md", "source_path": "dotnet-desktop-guide/framework/wpf/events/visual-basic-and-wpf-event-handling.md",
"redirect_url": "/dotnet/desktop/wpf/advanced/visual-basic-and-wpf-event-handling?view=netframeworkdesktop-4.8" "redirect_url": "/dotnet/desktop/wpf/advanced/visual-basic-and-wpf-event-handling?view=netframeworkdesktop-4.8"
},
{
"source_path": "dotnet-desktop-guide/net/wpf/advanced/preview-events.md",
"redirect_url": "/dotnet/desktop/wpf/events/preview-events?view=netdesktop-6.0"
},
{
"source_path": "dotnet-desktop-guide/framework/wpf/events/preview-events.md",
"redirect_url": "/dotnet/desktop/wpf/advanced/preview-events?view=netframeworkdesktop-4.8"
} }
] ]
} }
@@ -21,6 +21,10 @@ You can assign an event handler to an element in Windows Presentation Foundation
[!INCLUDE [desktop guide under construction](../../includes/desktop-guide-preview-note.md)] [!INCLUDE [desktop guide under construction](../../includes/desktop-guide-preview-note.md)]
## Prerequisites
The article assumes a basic knowledge of routed events, and that you've read [Routed events overview](/dotnet/desktop/wpf/advanced/routed-events-overview?view=netframeworkdesktop-4.8&preserve-view=true). To follow the examples in this article, it helps if you're familiar with Extensible Application Markup Language (XAML) and know how to write Windows Presentation Foundation (WPF) applications.
## Syntax for event handler assignment ## Syntax for event handler assignment
C# supports event handler assignment using: C# supports event handler assignment using:
@@ -31,7 +35,7 @@ C# supports event handler assignment using:
VB supports event handler assignment using: VB supports event handler assignment using:
- The [AddHandler](/dotnet/visual-basic/language-reference/statements/addhandler-statement) statement with the [AddressOf](/dotnet/visual-basic/language-reference/operators/addressof-operator) operator, which is also used in the CLR event handling model. - The [AddHandler](/dotnet/visual-basic/language-reference/statements/addhandler-statement) statement with the [AddressOf](/dotnet/visual-basic/language-reference/operators/addressof-operator) operator, which is also used in the CLR event handling model.
- The [Handles](/dotnet/visual-basic/language-reference/statements/handles-clause) keyword in the event handler definition. For more information, see [Visual Basic and WPF event handling](/dotnet/desktop/wpf/advanced/visual-basic-and-wpf-event-handling?view=netframeworkdesktop-4.8&preserve-view=true). - The [Handles](/dotnet/visual-basic/language-reference/statements/handles-clause) keyword in the event handler definition. For more information, see [Visual Basic and WPF event handling](visual-basic-and-wpf-event-handling.md).
- The <xref:System.Windows.UIElement.AddHandler%2A?displayProperty=nameWithType> method, together with the `AddressOf` operator to reference the event handler. - The <xref:System.Windows.UIElement.AddHandler%2A?displayProperty=nameWithType> method, together with the `AddressOf` operator to reference the event handler.
## Example ## Example
@@ -68,7 +72,7 @@ The `ButtonCreatedByCode_Click` event handler obtains the following information
> [!NOTE] > [!NOTE]
> A key difference between a routed event and a CLR event is that a routed event traverses the element tree, looking for handlers, whereas a CLR event doesn't traverse the element tree and handlers can only attach to the source object that raised the event. As a result, a routed event `sender` can be any traversed element in the element tree. > A key difference between a routed event and a CLR event is that a routed event traverses the element tree, looking for handlers, whereas a CLR event doesn't traverse the element tree and handlers can only attach to the source object that raised the event. As a result, a routed event `sender` can be any traversed element in the element tree.
For more information on how to create and handle routed events, see [How to create a custom routed event](/dotnet/desktop/wpf/advanced/how-to-create-a-custom-routed-event?view=netframeworkdesktop-4.8&preserve-view=true) and [Handle a routed event](/dotnet/desktop/wpf/advanced/how-to-handle-a-routed-event?view=netframeworkdesktop-4.8&preserve-view=true). For more information on how to create and handle routed events, see [How to create a custom routed event](how-to-create-a-custom-routed-event.md) and [Handle a routed event](/dotnet/desktop/wpf/advanced/how-to-handle-a-routed-event?view=netframeworkdesktop-4.8&preserve-view=true).
## See also ## See also
@@ -17,6 +17,10 @@ Windows Presentation Foundation (WPF) application developers and component autho
[!INCLUDE [desktop guide under construction](../../includes/desktop-guide-preview-note.md)] [!INCLUDE [desktop guide under construction](../../includes/desktop-guide-preview-note.md)]
## Prerequisites
The article assumes a basic knowledge of routed events, and that you've read [Routed events overview](/dotnet/desktop/wpf/advanced/routed-events-overview?view=netframeworkdesktop-4.8&preserve-view=true). To follow the examples in this article, it helps if you're familiar with Extensible Application Markup Language (XAML) and know how to write Windows Presentation Foundation (WPF) applications.
## Routed event steps ## Routed event steps
The basic steps to create a routed event are: The basic steps to create a routed event are:
@@ -0,0 +1,71 @@
---
title: "Preview events"
description: Learn about preview events in Windows Presentation Foundation (WPF) and how to use preview events for composite control event handling.
ms.date: "03/09/2022"
dev_langs:
- "csharp"
- "vb"
helpviewer_keywords:
- "Preview events [WPF]"
- "suppressing events [WPF]"
- "events [WPF], Preview"
- "events [WPF], suppressing"
---
<!-- The acrolinx score was 96 on 03/09/2021-->
# Preview events (WPF .NET)
Preview events, also known as tunneling events, are routed events that traverse downward through the element tree from the application root element to the element that raised the event. The element that raises an event is reported as the <xref:System.Windows.RoutedEventArgs.Source> in the event data. Not all event scenarios support or require preview events. This article describes where preview events exist and how applications or components can interact with them. For information on how to create a preview event, see [How to create a custom routed event](how-to-create-a-custom-routed-event.md).
[!INCLUDE [desktop guide under construction](../../includes/desktop-guide-preview-note.md)]
## Prerequisites
The article assumes a basic knowledge of routed events, and that you've read [Routed events overview](/dotnet/desktop/wpf/advanced/routed-events-overview?view=netframeworkdesktop-4.8&preserve-view=true). To follow the examples in this article, it helps if you're familiar with Extensible Application Markup Language (XAML) and know how to write Windows Presentation Foundation (WPF) applications.
## Preview events marked as handled
Be cautious when marking preview events as handled in event data. Marking a preview event as handled on an element other than the element that raised it can prevent the element that raised it from handling the event. Sometimes marking preview events as handled is intentional. For example, a composite control might suppress events raised by individual components and replace them with events raised by the complete control. Custom events for a control can provide customized event data and trigger based on component state relationships.
For [input events](/dotnet/desktop/wpf/advanced/input-overview), event data is shared by both the preview and non-preview (bubbling) equivalents of each event. If you use a preview event class-handler to mark an input event as handled, class-handlers for the bubbling input event typically won't be invoked. Or, if you use a preview event instance-handler to mark an event as handled, instance-handlers for the bubbling input event typically won't be invoked. Although you can configure class and instance handlers to be invoked even if an event is marked as handled, that handler configuration isn't common. For more information about class handling and how it relates to preview events, see [Marking routed events as handled and class handling](/dotnet/desktop/wpf/advanced/marking-routed-events-as-handled-and-class-handling?view=netframeworkdesktop-4.8&preserve-view=true).
> [!NOTE]
> Not all preview events are [tunneling](<xref:System.Windows.RoutingStrategy.Tunnel>) events. For example, the <xref:System.Windows.UIElement.PreviewMouseLeftButtonDown> input event follows a downward route through the element tree, but is a [direct](<xref:System.Windows.RoutingStrategy.Direct>) routed event that's raised and reraised by each <xref:System.Windows.UIElement> in the route.
## Preview events in composite controls
Preview events are commonly used by composite controls to handle input events raised by their components. For example, a composite control may suppress input events at the component level and replace them with one or more events that provide customized event data. For example, the WPF <xref:System.Windows.Controls.Primitives.ButtonBase> marks the <xref:System.Windows.UIElement.MouseLeftButtonDown> input event as handled in its <xref:System.Windows.UIElement.OnMouseLeftButtonDown%2A> method and raises the <xref:System.Windows.Controls.Primitives.ButtonBase.Click> event. The `MouseLeftButtonDown` event and its event data still continue along the element tree route, but because the event is marked as <xref:System.Windows.RoutedEventArgs.Handled%2A> in event data, only handlers that are configured to respond to handled events are invoked.
If you want other elements toward the root of your application to handle a routed event that's marked as handled, you can either:
- Attach handlers by calling the <xref:System.Windows.UIElement.AddHandler(System.Windows.RoutedEvent,System.Delegate,System.Boolean)?displayProperty=nameWithType> method and setting the parameter `handledEventsToo` to `true`. This approach requires attaching the event handler in code-behind, after obtaining an object reference to the element that it will attach to.
- If the event marked as handled is a bubbling event, attach handlers for the equivalent preview event if available. For instance, if a control suppresses the <xref:System.Windows.UIElement.MouseLeftButtonDown> event, you can attach a handler for the <xref:System.Windows.UIElement.PreviewMouseLeftButtonDown> event instead. This approach only works for base element input events that implement both [tunneling](<xref:System.Windows.RoutingStrategy.Tunnel>) and [bubbling](<xref:System.Windows.RoutingStrategy.Bubble>) routing strategies and share event data.
The following example implements a rudimentary custom control named `componentWrapper` that contains a <xref:System.Windows.Controls.TextBox>. The control is added to a <xref:System.Windows.Controls.StackPanel> named `outerStackPanel`.
:::code language="xaml" source="./snippets/preview-events/csharp/MainWindow.xaml" id="CustomControlEventSuppression":::
The `componentWrapper` control listens for the <xref:System.Windows.UIElement.KeyDown> bubbling event raised by its `TextBox` component whenever a keystroke occurs. On that occurrence, the `componentWrapper` control:
1. Marks the `KeyDown` bubbling routed event as handled to suppress it. As a result, only the `outerStackPanel` handler that's configured in code-behind to respond to _handled_ `KeyDown` events is triggered. The `outerStackPanel` handler attached in XAML for `KeyDown` events isn't invoked.
1. Raises a custom bubbling routed event named `CustomKey`, which triggers the `outerStackPanel` handler for the `CustomKey` event.
:::code language="csharp" source="./snippets/preview-events/csharp/MainWindow.xaml.cs" id="EventSuppressionWorkarounds":::
:::code language="vb" source="./snippets/preview-events/vb/MainWindow.xaml.vb" id="EventSuppressionWorkarounds":::
The example demonstrates two workarounds for getting the suppressed `KeyDown` routed event to invoke an event handler attached to the `outerStackPanel`:
- Attach a <xref:System.Windows.UIElement.PreviewKeyDown> event handler to the `outerStackPanel`. Since a preview input routed event precedes the equivalent bubbling routed event, the `PreviewKeyDown` handler in the example runs ahead of the `KeyDown` handler that suppresses both preview and bubbling events through their shared event data.
- Attach a `KeyDown` event handler to the `outerStackPanel` by using the <xref:System.Windows.UIElement.AddHandler(System.Windows.RoutedEvent,System.Delegate,System.Boolean)?displayProperty=nameWithType> method in code-behind, with the `handledEventsToo` parameter set to `true`.
> [!NOTE]
> Marking preview or non-preview equivalents of input events as handled are both strategies for suppressing events raised by the components of a control. The approach you use depends on your application requirements.
## See also
- [Marking routed events as handled, and class handling](/dotnet/desktop/wpf/advanced/marking-routed-events-as-handled-and-class-handling?view=netframeworkdesktop-4.8&preserve-view=true)
- [Routed events overview](/dotnet/desktop/wpf/advanced/routed-events-overview?view=netframeworkdesktop-4.8&preserve-view=true)
- [How to create a custom routed event](how-to-create-a-custom-routed-event.md)
@@ -0,0 +1,9 @@
<Application x:Class="CodeSampleCsharp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CodeSampleCsharp"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
@@ -0,0 +1,11 @@
using System.Windows;
namespace CodeSampleCsharp
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
}
@@ -0,0 +1,10 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<NeutralLanguage>en-us</NeutralLanguage>
</PropertyGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>
@@ -0,0 +1,23 @@
<Window x:Class="CodeSampleCsharp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom="clr-namespace:CodeSampleCsharp"
Title="Preview events" Height="100" Width="300">
<!--<CustomControlEventSuppression>-->
<StackPanel Name="outerStackPanel"
VerticalAlignment="Center"
custom:ComponentWrapper.CustomKey="Handler_PrintEventInfo"
TextBox.KeyDown="Handler_PrintEventInfo"
TextBox.PreviewKeyDown="Handler_PrintEventInfo" >
<custom:ComponentWrapper
x:Name="componentWrapper"
TextBox.KeyDown="ComponentWrapper_KeyDown"
custom:ComponentWrapper.CustomKey="Handler_PrintEventInfo"
HorizontalAlignment="Center">
<TextBox Name="componentTextBox" Width="200" KeyDown="Handler_PrintEventInfo" />
</custom:ComponentWrapper>
</StackPanel>
<!--</CustomControlEventSuppression>-->
</Window>
@@ -0,0 +1,95 @@
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
namespace CodeSampleCsharp
{
//<EventSuppressionWorkarounds>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// Attach a handler on outerStackPanel that will be invoked by handled KeyDown events.
outerStackPanel.AddHandler(KeyDownEvent, new RoutedEventHandler(Handler_PrintEventInfo),
handledEventsToo: true);
}
private void ComponentWrapper_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
Handler_PrintEventInfo(sender, e);
Debug.WriteLine("KeyDown event marked as handled on componentWrapper.\r\n" +
"CustomKey event raised on componentWrapper.");
// Mark the event as handled.
e.Handled = true;
// Raise the custom click event.
componentWrapper.RaiseCustomRoutedEvent();
}
private void Handler_PrintEventInfo(object sender, System.Windows.Input.KeyEventArgs e)
{
string senderName = ((FrameworkElement)sender).Name;
string sourceName = ((FrameworkElement)e.Source).Name;
string eventName = e.RoutedEvent.Name;
string handledEventsToo = e.Handled ? " Parameter handledEventsToo set to true." : "";
Debug.WriteLine($"Handler attached to {senderName} " +
$"triggered by {eventName} event raised on {sourceName}.{handledEventsToo}");
}
private void Handler_PrintEventInfo(object sender, RoutedEventArgs e)
{
string senderName = ((FrameworkElement)sender).Name;
string sourceName = ((FrameworkElement)e.Source).Name;
string eventName = e.RoutedEvent.Name;
string handledEventsToo = e.Handled ? " Parameter handledEventsToo set to true." : "";
Debug.WriteLine($"Handler attached to {senderName} " +
$"triggered by {eventName} event raised on {sourceName}.{handledEventsToo}");
}
// Debug output:
//
// Handler attached to outerStackPanel triggered by PreviewKeyDown event raised on componentTextBox.
// Handler attached to componentTextBox triggered by KeyDown event raised on componentTextBox.
// Handler attached to componentWrapper triggered by KeyDown event raised on componentTextBox.
// KeyDown event marked as handled on componentWrapper.
// CustomKey event raised on componentWrapper.
// Handler attached to componentWrapper triggered by CustomKey event raised on componentWrapper.
// Handler attached to outerStackPanel triggered by CustomKey event raised on componentWrapper.
// Handler attached to outerStackPanel triggered by KeyDown event raised on componentTextBox. Parameter handledEventsToo set to true.
}
public class ComponentWrapper : StackPanel
{
// Register a custom routed event using the Bubble routing strategy.
public static readonly RoutedEvent CustomKeyEvent =
EventManager.RegisterRoutedEvent(
name: "CustomKey",
routingStrategy: RoutingStrategy.Bubble,
handlerType: typeof(RoutedEventHandler),
ownerType: typeof(ComponentWrapper));
// Provide CLR accessors for assigning an event handler.
public event RoutedEventHandler CustomKey
{
add { AddHandler(CustomKeyEvent, value); }
remove { RemoveHandler(CustomKeyEvent, value); }
}
public void RaiseCustomRoutedEvent()
{
// Create a RoutedEventArgs instance.
RoutedEventArgs routedEventArgs = new(routedEvent: CustomKeyEvent);
// Raise the event, which will bubble up through the element tree.
RaiseEvent(routedEventArgs);
}
}
//</EventSuppressionWorkarounds>
}
@@ -0,0 +1,63 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace CodeSampleCsharp.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CodeSampleCsharp.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}
@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
@@ -0,0 +1,9 @@
<Application x:Class="Application"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CodeSampleVb"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
@@ -0,0 +1,6 @@
Class Application
' Application-level events, such as Startup, Exit, and DispatcherUnhandledException
' can be handled in this file.
End Class
@@ -0,0 +1,9 @@
'The ThemeInfo attribute describes where any theme specific and generic resource dictionaries can be found.
'1st parameter: where theme specific resource dictionaries are located
'(used if a resource is not found in the page,
' or application resource dictionaries)
'2nd parameter: where the generic resource dictionary is located
'(used if a resource is not found in the page,
'app, and any theme specific resource dictionaries)
<Assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)>
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<RootNamespace>CodeSampleVb</RootNamespace>
<NeutralLanguage>en-us</NeutralLanguage>
<UseWPF>true</UseWPF>
</PropertyGroup>
<ItemGroup>
<Import Include="System.Windows" />
<Import Include="System.Windows.Controls" />
<Import Include="System.Windows.Data" />
<Import Include="System.Windows.Documents" />
<Import Include="System.Windows.Input" />
<Import Include="System.Windows.Media" />
<Import Include="System.Windows.Media.Imaging" />
<Import Include="System.Windows.Navigation" />
<Import Include="System.Windows.Shapes" />
</ItemGroup>
</Project>
@@ -0,0 +1,23 @@
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom="clr-namespace:CodeSampleVb"
Title="Preview events" Height="100" Width="300">
<!--<CustomControlEventSuppression>-->
<StackPanel Name="outerStackPanel"
VerticalAlignment="Center"
custom:ComponentWrapper.CustomKey="Handler_PrintEventInfo"
TextBox.KeyDown="Handler_PrintEventInfo"
TextBox.PreviewKeyDown="Handler_PrintEventInfo" >
<custom:ComponentWrapper
x:Name="componentWrapper"
KeyDown="ComponentWrapper_KeyDown"
custom:ComponentWrapper.CustomKey="Handler_PrintEventInfo"
HorizontalAlignment="Center">
<TextBox x:Name="componentTextBox" Width="200" KeyDown="Handler_PrintEventInfo" />
</custom:ComponentWrapper>
</StackPanel>
<!--</CustomControlEventSuppression>-->
</Window>
@@ -0,0 +1,90 @@
'<EventSuppressionWorkarounds>
Partial Public Class MainWindow
Inherits Window
Public Sub New()
InitializeComponent()
' Attach a handler on outerStackPanel that will be invoked by handled KeyDown events.
outerStackPanel.[AddHandler](KeyDownEvent, New RoutedEventHandler(AddressOf Handler_PrintEventInfo),
handledEventsToo:=True)
End Sub
Private Sub ComponentWrapper_KeyDown(sender As Object, e As KeyEventArgs)
Handler_PrintEventInfo(sender, e)
Debug.WriteLine("KeyDown event marked as handled on componentWrapper." &
vbCrLf & "CustomKey event raised on componentWrapper.")
' Mark the event as handled.
e.Handled = True
' Raise the custom click event.
componentWrapper.RaiseCustomRoutedEvent()
End Sub
Private Sub Handler_PrintEventInfo(sender As Object, e As KeyEventArgs)
Dim senderName As String = CType(sender, FrameworkElement).Name
Dim sourceName As String = CType(e.Source, FrameworkElement).Name
Dim eventName As String = e.RoutedEvent.Name
Dim handledEventsToo As String = If(e.Handled, " Parameter handledEventsToo set to true.", "")
Debug.WriteLine($"Handler attached to {senderName} " &
$"triggered by {eventName} event raised on {sourceName}.{handledEventsToo}")
End Sub
Private Sub Handler_PrintEventInfo(sender As Object, e As RoutedEventArgs)
Dim senderName As String = CType(sender, FrameworkElement).Name
Dim sourceName As String = CType(e.Source, FrameworkElement).Name
Dim eventName As String = e.RoutedEvent.Name
Dim handledEventsToo As String = If(e.Handled, " Parameter handledEventsToo set to true.", "")
Debug.WriteLine($"Handler attached to {senderName} " &
$"triggered by {eventName} event raised on {sourceName}.{handledEventsToo}")
End Sub
' Debug output
'
' Handler attached to outerStackPanel triggered by PreviewKeyDown event raised on componentTextBox.
' Handler attached to componentTextBox triggered by KeyDown event raised on componentTextBox.
' Handler attached to componentWrapper triggered by KeyDown event raised on componentTextBox.
' KeyDown event marked as handled on componentWrapper.
' CustomKey event raised on componentWrapper.
' Handler attached to componentWrapper triggered by CustomKey event raised on componentWrapper.
' Handler attached to outerStackPanel triggered by CustomKey event raised on componentWrapper.
' Handler attached to outerStackPanel triggered by KeyDown event raised on componentTextBox. Parameter handledEventsToo set to true.
End Class
Public Class ComponentWrapper
Inherits StackPanel
' Register a custom routed event with the Bubble routing strategy.
Public Shared ReadOnly CustomKeyEvent As RoutedEvent =
EventManager.RegisterRoutedEvent(
name:="CustomKey",
routingStrategy:=RoutingStrategy.Bubble,
handlerType:=GetType(RoutedEventHandler),
ownerType:=GetType(ComponentWrapper))
' Provide CLR accessors to support event handler assignment.
Public Custom Event CustomKey As RoutedEventHandler
AddHandler(value As RoutedEventHandler)
[AddHandler](CustomKeyEvent, value)
End AddHandler
RemoveHandler(value As RoutedEventHandler)
[RemoveHandler](CustomKeyEvent, value)
End RemoveHandler
RaiseEvent(sender As Object, e As RoutedEventArgs)
[RaiseEvent](e)
End RaiseEvent
End Event
Public Sub RaiseCustomRoutedEvent()
' Create a RoutedEventArgs instance & raise the event,
' which will bubble up through the element tree.
Dim routedEventArgs As New RoutedEventArgs(routedEvent:=CustomKeyEvent)
[RaiseEvent](routedEventArgs)
End Sub
End Class
'</EventSuppressionWorkarounds>
@@ -14,6 +14,10 @@ If you're coding in Visual Basic .NET, you can use the language-specific [Handle
[!INCLUDE [desktop guide under construction](../../includes/desktop-guide-preview-note.md)] [!INCLUDE [desktop guide under construction](../../includes/desktop-guide-preview-note.md)]
## Prerequisites
The article assumes a basic knowledge of routed events, and that you've read [Routed events overview](/dotnet/desktop/wpf/advanced/routed-events-overview?view=netframeworkdesktop-4.8&preserve-view=true). To follow the examples in this article, it helps if you're familiar with Extensible Application Markup Language (XAML) and know how to write Windows Presentation Foundation (WPF) applications.
## Syntax ## Syntax
The syntax for a `Sub` declaration that uses the [Handles](/dotnet/visual-basic/language-reference/statements/handles-clause) keyword is: `Sub <procedure name> Handles <object name>.<event name>`. That syntax designates a procedure as the event handler that will run when an event specified by `<event name>` is raised on an object specified by `<object name>`. The event must be a member of the object's class or base class. The following example shows how to attach an event handler to a XAML element using `Handles`. The syntax for a `Sub` declaration that uses the [Handles](/dotnet/visual-basic/language-reference/statements/handles-clause) keyword is: `Sub <procedure name> Handles <object name>.<event name>`. That syntax designates a procedure as the event handler that will run when an event specified by `<event name>` is raised on an object specified by `<object name>`. The event must be a member of the object's class or base class. The following example shows how to attach an event handler to a XAML element using `Handles`.
+2
View File
@@ -80,6 +80,8 @@ items:
items: items:
- name: Events - name: Events
items: items:
- name: Preview events
href: events/preview-events.md
- name: Visual Basic and WPF event handling - name: Visual Basic and WPF event handling
href: events/visual-basic-and-wpf-event-handling.md href: events/visual-basic-and-wpf-event-handling.md
- name: Common tasks - name: Common tasks
+5
View File
@@ -343,6 +343,11 @@
"SourceUrl": "/dotnet/desktop/wpf/advanced/visual-basic-and-wpf-event-handling?view=netframeworkdesktop-4.8", "SourceUrl": "/dotnet/desktop/wpf/advanced/visual-basic-and-wpf-event-handling?view=netframeworkdesktop-4.8",
"TargetUrl": "/dotnet/desktop/wpf/events/visual-basic-and-wpf-event-handling?view=netdesktop-6.0" "TargetUrl": "/dotnet/desktop/wpf/events/visual-basic-and-wpf-event-handling?view=netdesktop-6.0"
}, },
{
"Redirect": "TwoWay",
"SourceUrl": "/dotnet/desktop/wpf/advanced/preview-events?view=netframeworkdesktop-4.8",
"TargetUrl": "/dotnet/desktop/wpf/events/preview-events?view=netdesktop-6.0"
},
// Systems - Properties // Systems - Properties
{ {