当前位置: 首页 > news >正文

WPF基础 | 深入 WPF 事件机制:路由事件与自定义事件处理

在这里插入图片描述
在这里插入图片描述

WPF基础 | 深入 WPF 事件机制:路由事件与自定义事件处理

  • 一、前言
  • 二、WPF 事件基础概念
    • 2.1 事件的定义与本质
    • 2.2 常见的 WPF 事件类型
  • 三、路由事件
    • 3.1 路由事件的概念与原理
    • 3.2 路由事件的三个阶段
    • 3.3 路由事件的标识与注册
    • 3.4 常见的路由事件示例
  • 四、自定义事件处理
    • 4.1 为什么需要自定义事件
    • 4.2 自定义路由事件的创建
    • 4.3 自定义非路由事件的创建
    • 4.4 自定义事件参数的传递
  • 五、路由事件与自定义事件处理的高级应用
    • 5.1 依赖属性与路由事件的结合
    • 5.2 自定义事件在 MVVM 模式中的应用
  • 六、路由事件和自定义事件的性能优化
    • 6.1 合理使用路由策略
    • 6.2 优化自定义事件的触发频率
    • 6.3 事件处理程序的优化
  • 结束语
  • 优质源码分享

WPF基础 | 深入 WPF 事件机制:路由事件与自定义事件处理 , 在 WPF 应用程序开发中,事件是用户与界面交互以及程序内部逻辑触发的关键媒介。通过事件,我们能够捕获用户的操作,如点击按钮、输入文本等,同时也能在程序特定状态变化时触发相应的处理逻辑。路由事件和自定义事件处理是 WPF 事件机制中极为重要的特性,它们赋予了开发者更大的灵活性和控制力,以构建复杂且高效的用户界面交互逻辑。深入理解并熟练运用这两种机制,对于开发高质量的 WPF 应用程序至关重要。

一、前言

    在数字浪潮汹涌澎湃的时代,程序开发宛如一座神秘而宏伟的魔法城堡,矗立在科技的浩瀚星空中。代码的字符,似那闪烁的星辰,按照特定的轨迹与节奏,组合、交织、碰撞,即将开启一场奇妙且充满无限可能的创造之旅。当空白的文档界面如同深邃的宇宙等待探索,程序员们则化身无畏的星辰开拓者,指尖在键盘上轻舞,准备用智慧与逻辑编织出足以改变世界运行规则的程序画卷,在 0 和 1 的二进制世界里,镌刻下属于人类创新与突破的不朽印记。

    在当今数字化时代,桌面应用程序的用户界面(UI)设计至关重要,它直接影响着用户体验与产品的竞争力。而 WPF(Windows Presentation Foundation)作为微软推出的一款强大的 UI 框架,其布局系统更是构建精美界面的核心要素。WPF 布局系统为开发者提供了丰富多样的布局方式,能够轻松应对各种复杂的界面设计需求,无论是简洁明了的工具软件,还是功能繁杂的企业级应用,都能借助其打造出令人惊艳的视觉效果与流畅的交互体验。

    在 WPF 的布局体系中,Grid 和 StackPanel 堪称两颗耀眼的明星,它们各自拥有独特的布局特性,适用于截然不同的场景,为开发者提供了灵活多变的布局选择。Grid 以其类似表格的网格结构,能够精准地对界面进行行列划分,轻松实现复杂的布局架构,无论是多模块的信息展示,还是不同区域的精细排版,Grid 都能游刃有余地应对;而 StackPanel 则专注于简单高效的线性排列,将子元素按照水平或垂直方向依次堆叠,适用于那些需要快速搭建、布局逻辑相对单一的界面部分,如导航栏、按钮组等。深入理解并熟练掌握这两种布局控件,对于打造优质的 WPF 应用界面而言,无疑是迈出了坚实且关键的一步。接下来,让我们一同深入探究它们的奥秘。

    WPF从入门到精通专栏,旨在为读者呈现一条从对 WPF(Windows Presentation Foundation)技术懵懂无知到精通掌握的学习路径。首先从基础入手,介绍 WPF 的核心概念,涵盖其独特的架构特点、开发环境搭建流程,详细解读布局系统、常用控件以及事件机制等基础知识,帮助初学者搭建起对 WPF 整体的初步认知框架。随着学习的深入,进阶部分聚焦于数据绑定、样式模板、动画特效等关键知识点,进一步拓展 WPF 开发的能力边界,使开发者能够打造出更为个性化、交互性强的桌面应用界面。高级阶段则涉及自定义控件开发、MVVM 设计模式应用、多线程编程等深层次内容,助力开发者应对复杂的业务需求,构建大型且可维护的应用架构。同时,通过实战项目案例解析,展示如何将所学知识综合运用到实际开发中,从需求分析到功能实现再到优化测试,全方位积累实践经验。此外,还探讨了性能优化、与其他技术集成以及安全机制等拓展性话题,让读者对 WPF 技术在不同维度有更深入理解,最终实现对 WPF 技术的精通掌握,具备独立开发高质量桌面应用的能力。

🛕 点击进入WPF从入门到精通专栏

在这里插入图片描述

二、WPF 事件基础概念

2.1 事件的定义与本质

    在 WPF 中,事件是一种特殊的委托,它允许对象在特定情况发生时通知其他对象。从本质上讲,事件是一种发布 - 订阅机制,事件的发布者(引发事件的对象)在特定条件满足时发布事件,而事件的订阅者(注册了事件处理程序的对象)则在接收到事件通知后执行相应的处理逻辑。例如,ButtonClick 事件,当用户点击按钮时,按钮对象作为发布者发布 Click 事件,而注册了该事件处理程序的代码部分则作为订阅者执行相应的点击处理逻辑。

2.2 常见的 WPF 事件类型

  • 鼠标事件

    鼠标事件是用户通过鼠标与界面交互时触发的事件,常见的包括 MouseDown(鼠标按下)、MouseUp(鼠标释放)、MouseMove(鼠标移动)等。例如,在一个 Canvas 上监听 MouseMove 事件,可以实时获取鼠标在 Canvas 上的位置信息,从而实现一些绘图或者交互效果。

<Canvas MouseMove="Canvas_MouseMove"><!-- Canvas 内容 -->
</Canvas>private void Canvas_MouseMove(object sender, MouseEventArgs e)
{Point position = e.GetPosition((IInputElement)sender);// 处理鼠标位置相关逻辑
}
  • 键盘事件

    键盘事件是用户通过键盘输入触发的事件,如 KeyDown(按键按下)、KeyUp(按键释放)等。在文本输入场景中,通过监听 KeyDown 事件可以实现一些快捷键功能,比如按下 Ctrl + S 实现保存操作。

<TextBox KeyDown="TextBox_KeyDown"><!-- 文本框内容 -->
</TextBox>private void TextBox_KeyDown(object sender, KeyEventArgs e)
{if (e.Key == Key.S && Keyboard.Modifiers == ModifierKeys.Control){// 执行保存操作逻辑}
}
  • 焦点事件

    焦点事件与控件获取或失去焦点相关,主要有 GotFocus(获得焦点)和 LostFocus(失去焦点)。例如,在一个登录界面中,当用户名 TextBox 获得焦点时,可以清除其默认提示文本,当失去焦点时,检查输入格式是否正确。

<TextBox x:Name="usernameTextBox" GotFocus="usernameTextBox_GotFocus" LostFocus="usernameTextBox_LostFocus"><TextBox.Text>请输入用户名</TextBox.Text>
</TextBox>private void usernameTextBox_GotFocus(object sender, RoutedEventArgs e)
{if (usernameTextBox.Text == "请输入用户名"){usernameTextBox.Text = "";}
}private void usernameTextBox_LostFocus(object sender, RoutedEventArgs e)
{if (string.IsNullOrEmpty(usernameTextBox.Text)){// 提示用户名不能为空}
}

三、路由事件

3.1 路由事件的概念与原理

    路由事件是 WPF 特有的一种事件机制,它能够在元素树中按照特定的路由策略传播事件。与传统的直接事件(如 Windows Forms 中的事件)不同,路由事件不仅仅局限于引发事件的对象本身,还可以在元素树的父元素或子元素之间传递。这种机制使得在处理复杂的用户界面交互时,能够以一种更高效、更灵活的方式进行事件处理。路由事件的传播基于元素树的结构,通过三个阶段进行:冒泡阶段、直接阶段和隧道阶段。

3.2 路由事件的三个阶段

  • 冒泡阶段

    在冒泡阶段,事件从引发事件的源元素开始,向上逐层传递到其父元素,直到到达根元素或者被某个元素处理为止。例如,在一个包含多个嵌套 ButtonStackPanel 中,如果最内层的 Button 被点击,Click 事件会首先在该 Button 上触发,然后依次向上传递到包含它的 StackPanel 以及更上层的父元素。这种机制类似于气泡从水底向上冒的过程,因此得名冒泡阶段。

<StackPanel MouseDown="StackPanel_MouseDown"><Button Content="内层按钮" Click="InnerButton_Click"/>
</StackPanel>private void InnerButton_Click(object sender, RoutedEventArgs e)
{// 内层按钮点击处理逻辑
}private void StackPanel_MouseDown(object sender, MouseButtonEventArgs e)
{// StackPanel 鼠标按下处理逻辑
}

    在上述代码中,当点击内层按钮时,首先会执行InnerButton_Click方法,然后如果InnerButton_Click方法没有将e.Handled设置为true,则会继续执行StackPanel_MouseDown方法。

  • 直接阶段

    直接阶段是指事件直接在引发事件的源元素上处理,不涉及元素树的向上或向下传递。在某些情况下,开发者可能希望只在事件源本身进行处理,而不希望事件进行冒泡或隧道传递,这时就可以利用直接阶段的特性。例如,对于一些特定的自定义控件,其内部的某些操作只需要在控件自身范围内处理,不需要影响到其父元素或子元素。

  • 隧道阶段

    隧道阶段与冒泡阶段相反,事件从根元素开始,沿着元素树向下传递到引发事件的源元素。这个阶段通常用于在事件到达目标元素之前进行一些预处理操作,比如拦截或者修改事件参数。例如,在一个包含多个文本框的窗口中,希望在任何文本框获得焦点之前,先检查当前是否有其他操作正在进行,如果有则阻止焦点获取。可以通过在窗口的 PreviewGotFocus 事件(隧道阶段事件)中进行处理。

<Window PreviewGotFocus="Window_PreviewGotFocus"><StackPanel><TextBox/><TextBox/></StackPanel>
</Window>private void Window_PreviewGotFocus(object sender, RoutedEventArgs e)
{if (IsSomeOperationInProgress){e.Handled = true;}
}

3.3 路由事件的标识与注册

  • 路由事件的标识

    在 WPF 中,每个路由事件都有一个唯一的标识符,它是一个RoutedEvent类型的静态字段。例如,Button 的 Click 事件的标识符是Button.ClickEvent。通过这个标识符,我们可以在代码中引用和注册该路由事件。

  • 路由事件的注册

    路由事件的注册方式有两种,一种是在 XAML 中通过属性语法进行注册,另一种是在代码背后文件中通过代码进行注册。

    XAML 注册:在 XAML 中,我们可以直接在元素的属性中指定事件处理方法,例如<Button Content="点击" Click="Button_Click"/>,这里Button_Click是在代码背后文件中定义的事件处理方法。

代码注册:在代码背后文件中,可以使用AddHandler方法来注册路由事件。例如:

public partial class MainWindow : Window
{public MainWindow(){InitializeComponent();myButton.AddHandler(Button.ClickEvent, new RoutedEventHandler(Button_Click));}private void Button_Click(object sender, RoutedEventArgs e){// 按钮点击处理逻辑}
}

3.4 常见的路由事件示例

  • Button 的 Click 事件

    ButtonClick 事件是一个典型的冒泡路由事件。当用户点击 Button 时,Click 事件首先在 Button 自身触发,然后会向上冒泡到其父元素。例如,在一个包含多个 ButtonGrid 中,点击其中一个 Button,不仅该 ButtonClick 事件处理程序会被执行,如果 Grid 也注册了 Click 事件处理程序,并且 Button 的 Click 事件处理程序没有将e.Handled设置为true,那么 GridClick 事件处理程序也会被执行。

  • TextBox 的 TextChanged 事件

    TextBox 的 TextChanged 事件也是路由事件。当 TextBox 中的文本发生改变时,该事件会在 TextBox 上触发,并可以根据需要进行冒泡。例如,在一个包含多个 TextBox 的 UserControl 中,如果希望在任何一个 TextBox 文本改变时,都对整个 UserControl 进行一些状态更新,可以在 UserControl 上注册 TextBox.TextChanged 事件的处理程序。

<UserControl TextBox.TextChanged="UserControl_TextBoxTextChanged"><StackPanel><TextBox/><TextBox/></StackPanel>
</UserControl>private void UserControl_TextBoxTextChanged(object sender, TextChangedEventArgs e)
{// UserControl 中文本框文本改变处理逻辑
}

四、自定义事件处理

4.1 为什么需要自定义事件

    虽然 WPF 提供了丰富的内置事件,但在实际开发中,我们可能会遇到一些特定的业务场景,需要定义自己的事件来满足需求。例如,在一个自定义的图表控件中,当数据点发生特定的变化时,我们希望能够通知其他部分的程序进行相应的处理,这时就需要自定义一个事件来实现这种通知机制。自定义事件可以让我们更精准地控制事件的触发条件、参数传递以及处理逻辑,从而提高代码的可维护性和扩展性。

4.2 自定义路由事件的创建

  • 定义事件标识符

    首先,需要在自定义控件类中定义一个RoutedEvent类型的静态字段作为事件标识符。例如,创建一个自定义的MyCustomEvent事件:

public class MyCustomControl : Control
{public static readonly RoutedEvent MyCustomEvent =EventManager.RegisterRoutedEvent("MyCustomEvent", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyCustomControl));
}

    在上述代码中,RegisterRoutedEvent方法用于注册路由事件,第一个参数是事件名称,第二个参数指定路由策略(这里是冒泡策略),第三个参数是事件处理程序的委托类型,第四个参数是定义该事件的控件类型。

  • 定义事件包装器

    为了方便在 XAML 中使用和在代码中调用,需要为自定义路由事件定义一个事件包装器。事件包装器是一个普通的event声明,它使用前面定义的事件标识符。

public class MyCustomControl : Control
{public static readonly RoutedEvent MyCustomEvent =EventManager.RegisterRoutedEvent("MyCustomEvent", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyCustomControl));public event RoutedEventHandler MyCustomEvent{add { AddHandler(MyCustomEvent, value); }remove { RemoveHandler(MyCustomEvent, value); }}
}
  • 触发自定义事件

    在自定义控件的适当位置,根据业务逻辑触发自定义事件。例如,在MyCustomControl的某个方法中触发MyCustomEvent事件:

public class MyCustomControl : Control
{public static readonly RoutedEvent MyCustomEvent =EventManager.RegisterRoutedEvent("MyCustomEvent", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyCustomControl));public event RoutedEventHandler MyCustomEvent{add { AddHandler(MyCustomEvent, value); }remove { RemoveHandler(MyCustomEvent, value); }}public void DoSomething(){// 执行一些操作RoutedEventArgs newEventArgs = new RoutedEventArgs(MyCustomEvent, this);RaiseEvent(newEventArgs);}
}

4.3 自定义非路由事件的创建

  • 定义事件委托与事件

    对于非路由事件,首先需要定义一个委托类型来表示事件处理程序的签名,然后在类中声明一个该委托类型的事件。例如,创建一个简单的自定义非路由事件:

public delegate void MyNonRoutedEventHandler(object sender, EventArgs e);public class MyNonRoutedControl
{public event MyNonRoutedEventHandler MyNonRoutedEvent;public void DoAnotherThing(){if (MyNonRoutedEvent!= null){MyNonRoutedEvent(this, EventArgs.Empty);}}
}
  • 注册与处理自定义非路由事件

    在使用自定义非路由控件的地方,可以注册并处理该事件。例如:

public partial class MainWindow : Window
{public MainWindow(){InitializeComponent();MyNonRoutedControl myControl = new MyNonRoutedControl();myControl.MyNonRoutedEvent += MyControl_MyNonRoutedEvent;}private void MyControl_MyNonRoutedEvent(object sender, EventArgs e){// 处理自定义非路由事件逻辑}
}

4.4 自定义事件参数的传递

  • 自定义事件参数类

    在很多情况下,我们需要在触发事件时传递一些额外的信息,这就需要自定义事件参数类。自定义事件参数类通常继承自EventArgs类。例如,创建一个用于传递自定义数据的事件参数类:

public class MyCustomEventArgs : EventArgs
{public string CustomData { get; set; }public MyCustomEventArgs(string data){CustomData = data;}
}
  • 在事件中使用自定义事件参数

    在自定义事件的触发和处理过程中,使用自定义事件参数类来传递数据。例如,对于前面定义的自定义非路由事件,修改为使用自定义事件参数:

public delegate void MyNonRoutedEventHandler(object sender, MyCustomEventArgs e);public class MyNonRoutedControl
{public event MyNonRoutedEventHandler MyNonRoutedEvent;public void DoAnotherThing(){if (MyNonRoutedEvent!= null){MyCustomEventArgs args = new MyCustomEventArgs("自定义数据");MyNonRoutedEvent(this, args);}}
}public partial class MainWindow : Window
{public MainWindow(){InitializeComponent();MyNonRoutedControl myControl = new MyNonRoutedControl();myControl.MyNonRoutedEvent += MyControl_MyNonRoutedEvent;}private void MyControl_MyNonRoutedEvent(object sender, MyCustomEventArgs e){string data = e.CustomData;// 处理自定义数据逻辑}
}

五、路由事件与自定义事件处理的高级应用

5.1 依赖属性与路由事件的结合

  • 依赖属性影响路由事件行为

    依赖属性与路由事件常常结合使用,依赖属性的值可以影响路由事件的触发条件或处理逻辑。例如,在一个自定义的开关控件中,有一个依赖属性IsOn表示开关的状态,当IsOn的值发生改变时,触发一个自定义路由事件SwitchStateChanged

public class SwitchControl : Control
{public static readonly DependencyProperty IsOnProperty =DependencyProperty.Register("IsOn", typeof(bool), typeof(SwitchControl), new PropertyMetadata(false, OnIsOnPropertyChanged));public bool IsOn{get { return (bool)GetValue(IsOnProperty); }set { SetValue(IsOnProperty, value); }}public static readonly RoutedEvent SwitchStateChangedEvent =EventManager.RegisterRoutedEvent("SwitchStateChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(SwitchControl));public event RoutedEventHandler SwitchStateChanged{add { AddHandler(SwitchStateChangedEvent, value); }remove { RemoveHandler(SwitchStateChangedEvent, value); }}private static void OnIsOnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e){SwitchControl switchControl = (SwitchControl)d;bool oldValue = (bool)e.OldValue;bool newValue = (bool)e.NewValue;if (oldValue!= newValue){RoutedEventArgs args = new RoutedEventArgs(SwitchStateChangedEvent, switchControl);switchControl.RaiseEvent(args);}}
}
  • 通过绑定控制路由事件

    可以通过数据绑定将依赖属性与其他控件或数据源进行关联,从而间接控制路由事件的触发。例如,将一个 SliderValue 属性绑定到上述开关控件的IsOn属性,当 Slider 的值改变时,开关控件的状态也会改变,进而触发SwitchStateChanged事件。

<StackPanel><Slider x:Name="slider" Minimum="0" Maximum="1" Value="{Binding ElementName=switchControl, Path=IsOn, Mode=TwoWay}"/><local:SwitchControl x:Name="switchControl"/>
</StackPanel>

5.2 自定义事件在 MVVM 模式中的应用

  • MVVM 模式简介

    MVVM(Model - View - ViewModel)是一种软件架构模式,它将应用程序分为三个主要部分:模型(Model),包含应用程序的数据和业务逻辑;视图(View),负责呈现用户界面;视图模型(ViewModel),作为视图和模型之间的桥梁,负责处理视图和模型之间的数据绑定和交互逻辑。在 MVVM 模式中,视图和模型之间不直接通信,而是通过视图模型进行交互,这有助于实现代码的分离和可维护性。

  • 在 MVVM 中使用自定义路由事件

    在 MVVM 模式下的自定义控件中,自定义路由事件可以用于在视图和视图模型之间传递特定的交互信息。例如,假设有一个自定义的DataGridView控件,当用户在表格中双击某一行数据时,需要通知视图模型进行相应的处理,如显示该行数据的详细信息。

    首先,在自定义控件DataGridView中定义并实现自定义路由事件:

public class DataGridView : Control
{public static readonly RoutedEvent RowDoubleClickEvent =EventManager.RegisterRoutedEvent("RowDoubleClick", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(DataGridView));public event RoutedEventHandler RowDoubleClick{add { AddHandler(RowDoubleClickEvent, value); }remove { RemoveHandler(RowDoubleClickEvent, value); }}// 假设这里有处理双击事件的逻辑,并触发自定义事件private void HandleDoubleClick(){RoutedEventArgs args = new RoutedEventArgs(RowDoubleClickEvent, this);RaiseEvent(args);}
}

    在视图(XAML)中,将自定义控件与视图模型进行绑定,并注册自定义事件的处理:

<local:DataGridView RowDoubleClick="{Binding RowDoubleClickCommand}"><!-- DataGridView 的其他内容 -->
</local:DataGridView>

    在视图模型中,定义RowDoubleClickCommand命令来处理自定义事件:

public class DataGridViewViewModel
{public ICommand RowDoubleClickCommand { get; private set; }public DataGridViewViewModel(){RowDoubleClickCommand = new RelayCommand(OnRowDoubleClick);}private void OnRowDoubleClick(object parameter){// 处理双击行的逻辑,例如获取行数据并显示详细信息}
}

    这里通过自定义路由事件,实现了视图和视图模型之间的解耦交互,符合 MVVM 模式的设计理念。

  • 在 MVVM 中使用自定义非路由事件

    自定义非路由事件同样可以在 MVVM 模式中发挥作用。例如,在一个视图模型中,当某个属性的值发生特定变化时,可能需要通知视图进行一些可视化的更新,但这种通知不需要在元素树中传播。

    在视图模型类中定义自定义非路由事件:

public class MyViewModel
{public delegate void SpecialPropertyChangedEventHandler(object sender, EventArgs e);public event SpecialPropertyChangedEventHandler SpecialPropertyChanged;private string _specialProperty;public string SpecialProperty{get { return _specialProperty; }set{if (_specialProperty!= value){_specialProperty = value;if (SpecialPropertyChanged!= null){SpecialPropertyChanged(this, EventArgs.Empty);}}}}
}

    在视图(XAML)的代码背后文件中,注册视图模型的自定义非路由事件:

public partial class MainWindow : Window
{private MyViewModel _viewModel;public MainWindow(){InitializeComponent();_viewModel = new MyViewModel();_viewModel.SpecialPropertyChanged += ViewModel_SpecialPropertyChanged;DataContext = _viewModel;}private void ViewModel_SpecialPropertyChanged(object sender, EventArgs e){// 根据视图模型属性变化更新视图的逻辑}
}

六、路由事件和自定义事件的性能优化

6.1 合理使用路由策略

  • 选择合适的路由阶段

    在定义路由事件时,应根据实际需求选择合适的路由策略。如果只需要在事件源本身处理事件,可选择直接路由策略,这样可以避免不必要的事件传播,提高性能。例如,对于一些内部操作的事件,如自定义控件内部的状态更新事件,使用直接路由策略可以减少性能开销。
如果希望事件能够向上传递给父元素进行统一处理,如在一个包含多个子控件的容器中,当任何子控件发生特定事件时,容器都需要做出响应,这时应选择冒泡路由策略。而隧道路由策略适用于在事件到达目标元素之前进行预处理的场景,如全局的输入验证等。避免滥用冒泡或隧道路由策略,因为过多的事件传播会增加系统的开销。

  • 避免不必要的事件冒泡或隧道

    在事件处理过程中,及时设置e.Handled = true可以阻止事件继续冒泡或隧道。例如,在一个复杂的界面中,如果某个按钮的点击事件已经在按钮自身的处理程序中完成了所有必要的操作,就应该将e.Handled设置为true,防止事件继续向上冒泡到父元素,从而减少不必要的事件处理调用,提高性能。

6.2 优化自定义事件的触发频率

  • 防抖与节流

    对于自定义事件,尤其是那些可能会频繁触发的事件,如自定义图表控件中数据点实时变化的事件,使用防抖(Debounce)或节流(Throttle)技术可以有效优化性能。

    防抖:防抖是指在事件触发后,等待一定时间(例如 200 毫秒),如果在这段时间内事件没有再次触发,则执行相应的处理逻辑。如果在等待时间内事件再次触发,则重新开始计时。这样可以避免在短时间内频繁触发事件导致的性能问题。例如,在一个搜索框的文本改变事件中,使用防抖技术可以避免用户每次输入一个字符都触发搜索操作,而是在用户停止输入一段时间后再进行搜索。

    节流:节流是指在一定时间间隔内,无论事件触发多少次,都只执行一次处理逻辑。例如,在一个实时更新的图表中,数据可能会频繁变化,但我们不需要每次数据变化都重新绘制图表,可以通过节流技术,每隔一定时间(如 500 毫秒)进行一次图表更新,这样既能保证图表的实时性,又能减少不必要的绘制操作,提高性能。

6.3 事件处理程序的优化

  • 简化事件处理逻辑

    事件处理程序应尽量简洁,避免在其中执行复杂的、耗时的操作。如果确实需要进行复杂操作,应考虑将其放在后台线程中执行,以避免阻塞主线程,影响用户界面的响应性。例如,在按钮的点击事件处理程序中,如果需要进行文件读取或网络请求等耗时操作,应使用异步编程模型(如async和await关键字)将这些操作放在后台线程执行。

  • 减少事件处理程序的内存占用

    在事件处理过程中,注意避免创建过多不必要的对象。例如,在事件处理程序中尽量复用已有的对象,而不是每次都创建新的对象。同时,及时释放不再使用的对象资源,防止内存泄漏。对于一些长时间运行的应用程序,这一点尤为重要,因为随着时间的推移,内存泄漏可能会导致应用程序性能下降甚至崩溃。

结束语

        展望未来,WPF 布局系统依然有着广阔的发展前景。随着硬件技术的不断革新,如高分辨率屏幕、折叠屏设备的日益普及,WPF 布局系统有望进一步强化其自适应能力,为用户带来更加流畅、一致的体验。在应对高分辨率屏幕时,能够更加智能地缩放和布局元素,确保文字清晰可读、图像不失真;对于折叠屏设备,可动态调整布局结构,充分利用多屏空间,实现无缝切换。

        WPF 的路由事件和自定义事件处理机制为开发者提供了强大而灵活的工具,用于构建复杂的用户界面交互逻辑。通过深入理解事件的基本概念、路由事件的传播机制、自定义事件的创建与应用,以及在 MVVM 模式中的实践,开发者能够更好地实现代码的分离、可维护性和扩展性。同时,关注路由事件和自定义事件的性能优化,对于打造高效、流畅的 WPF 应用程序至关重要。在实际开发中,应根据具体的业务需求,合理运用这些机制和优化策略,以提供优质的用户体验。随着 WPF 技术的不断发展,事件机制可能会有更多的改进和扩展,开发者需要持续关注并学习,以不断提升自己的开发能力。

        性能优化方面,微软及广大开发者社区将持续努力,进一步降低复杂布局的计算开销,提高布局更新的效率,使得 WPF 应用在处理大规模数据、动态界面时依然能够保持高效响应。通过改进算法、优化内存管理等手段,让 WPF 布局系统在性能上更上一层楼。

        亲爱的朋友,无论前路如何漫长与崎岖,都请怀揣梦想的火种,因为在生活的广袤星空中,总有一颗属于你的璀璨星辰在熠熠生辉,静候你抵达。

         愿你在这纷繁世间,能时常收获微小而确定的幸福,如春日微风轻拂面庞,所有的疲惫与烦恼都能被温柔以待,内心永远充盈着安宁与慰藉。

        至此,文章已至尾声,而您的故事仍在续写,不知您对文中所叙有何独特见解?期待您在心中与我对话,开启思想的新交流。


--------------- 业精于勤,荒于嬉 ---------------

请添加图片描述

--------------- 行成于思,毁于随 ---------------

优质源码分享

  • 【百篇源码模板】html5各行各业官网模板源码下载

  • 【模板源码】html实现酷炫美观的可视化大屏(十种风格示例,附源码)

  • 【VUE系列】VUE3实现个人网站模板源码

  • 【HTML源码】HTML5小游戏源码

  • 【C#实战案例】C# Winform贪吃蛇小游戏源码


在这里插入图片描述


     💞 关注博主 带你实现畅游前后端

     🏰 大屏可视化 带你体验酷炫大屏

     💯 神秘个人简介 带你体验不一样得介绍

     🎀 酷炫邀请函 带你体验高大上得邀请


     ① 🉑提供云服务部署(有自己的阿里云);
     ② 🉑提供前端、后端、应用程序、H5、小程序、公众号等相关业务;
     如🈶合作请联系我,期待您的联系。
    :本文撰写于CSDN平台,作者:xcLeigh所有权归作者所有) ,https://blog.csdn.net/weixin_43151418,如果相关下载没有跳转,请查看这个地址,相关链接没有跳转,皆是抄袭本文,转载请备注本文原地址。


     亲,码字不易,动动小手,欢迎 点赞 ➕ 收藏,如 🈶 问题请留言(评论),博主看见后一定及时给您答复,💌💌💌


原文地址:https://blog.csdn.net/weixin_43151418/article/details/145303354(防止抄袭,原文地址不可删除)

相关文章:

WPF基础 | 深入 WPF 事件机制:路由事件与自定义事件处理

WPF基础 | 深入 WPF 事件机制&#xff1a;路由事件与自定义事件处理 一、前言二、WPF 事件基础概念2.1 事件的定义与本质2.2 常见的 WPF 事件类型 三、路由事件3.1 路由事件的概念与原理3.2 路由事件的三个阶段3.3 路由事件的标识与注册3.4 常见的路由事件示例 四、自定义事件处…...

CAP原则中的P:分区容错性(Partition tolerance)

CAP 理论概述 在分布式系统中&#xff0c;系统面临三大核心问题&#xff1a; 一致性&#xff08;Consistency&#xff09;&#xff1a;所有节点的数据始终保持一致。可用性&#xff08;Availability&#xff09;&#xff1a;系统始终能够响应请求&#xff0c;无论请求是否成功…...

校验收货地址是否超出配送范围实战3(day09)

优化用户下单功能&#xff0c;加入校验逻辑&#xff0c;如果用户的收货地址距离商家门店超出配送范围&#xff08;配送范围为5公里内&#xff09;&#xff0c;则下单失败。 提示&#xff1a; ​ 1. 基于百度地图开放平台实现&#xff08;https://lbsyun.baidu.com/&#xff09…...

【二叉树的深搜】二叉树剪枝

文章目录 814. 二叉树剪枝解题思路&#xff1a;深度优先遍历 后序遍历另一种写法 814. 二叉树剪枝 814. 二叉树剪枝 ​ 给你二叉树的根结点 root &#xff0c;此外树的每个结点的值要么是 0 &#xff0c;要么是 1 。 ​ 返回移除了所有不包含 1 的子树的原二叉树。 ​ 节点…...

Java 大视界 -- Java 大数据中的数据脱敏技术与合规实践(60)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…...

k8s优雅重启

理论上处于terminating状态的pod&#xff0c;k8s 就会把它从service中移除了&#xff0c;只用配置一个优雅停机时长就行了。kubectl get endpoints 验证 简介 使用kubernetes启动容器时&#xff0c;一般都会配置一些探针来保证pod的健康&#xff0c;并通过terminationGracePe…...

RK3568上电启动流程详解.2 [十五]

这里我们再深入分析一下 U-Boot 启动 Kernel 的过程。 我们首先需要从 eMMC 的 boot partition 中加载 kernel Image&#xff0c;kernel dtb 以及 Ramdisk&#xff1a; > mmc partPartition Map for MMC device 0 -- Partition Type: EFIPart Start LBA End L…...

仅仅4M!windows系统适用,免费无限制使用!

软件介绍 在日常生活里&#xff0c;我们经常会碰到电脑运行迟缓、网速卡顿的现象&#xff0c;却又不清楚是哪个程序在占用过多资源。这种时候&#xff0c;一款能实时监测网络和系统状态的工具就变得非常关键了。今天呢&#xff0c;就给大家介绍一个小巧又实用的监控工具——「T…...

SOME/IP服务接口

本系列文章将分享我在学习 SOME/IP 过程中积累的一些感悟&#xff0c;并结合 SOME/IP 的理论知识进行讲解。主要内容是对相关知识的梳理&#xff0c;并结合实际代码展示 SOME/IP 的使用&#xff0c;旨在自我复习并与大家交流。文中引用了一些例图&#xff0c;但由于未能找到原作…...

计算机网络之网络层

本文章目录结构出自于《王道计算机考研 计算机网络_哔哩哔哩_bilibili》 03 网络层 在网上看到其他人做了相关笔记&#xff0c;就不再多余写了&#xff0c;直接参考着学习吧。 1 详解网络层-网络层概述和编址【王道计算机网络笔记】_wx63088f6683f8f的技术博客_51CTO博客 2 …...

WebAssembly视频检测在社区创作平台的落地与实践 | 得物技术

一、背景&现状 创作者服务平台作为得物为社区创作者提供的PC端视频发布入口&#xff0c;地位非常重要。且随着功能的升级迭代&#xff0c;用户群体也越来越多。但我们偶尔会收到如下反馈&#xff1a; 视频损坏&#xff0c;无法播放视频模糊曝光度问题黑屏&#xff0c;只有…...

服务器内部是如何运行的

服务器内部的运行可以从硬件和软件两个方面来解释。 一、硬件层面 服务器的硬件与普通计算机相似,但它通常具有更高的性能和更强的扩展性。服务器硬件包括: 1.中央处理单元(CPU):负责执行服务器上的计算任务。服务器一般配备多核心的高性能CPU,以支持多个请求并行处理…...

mapbox加载geojson,鼠标移入改变颜色,设置样式以及vue中的使用

全国地图json数据下载地址 目录 html加载全部代码 方式一&#xff1a;使用html方式加载geojson 1. 初始化地图 2. 加载geojson数据 设置geojson图层样式&#xff0c;设置type加载数据类型 设置线条 鼠标移入改变颜色&#xff0c;设置图层属性&#xff0c;此处是fill-extru…...

电子应用设计方案102:智能家庭AI鱼缸系统设计

智能家庭 AI 鱼缸系统设计 一、引言 智能家庭 AI 鱼缸系统旨在为鱼类提供一个健康、舒适的生活环境&#xff0c;同时为用户提供便捷的管理和观赏体验。 二、系统概述 1. 系统目标 - 自动维持水质稳定&#xff0c;包括水温、酸碱度、硬度和溶氧量等关键指标。 - 智能投食&…...

ESP8266 OTA固件启动日志里分区解析【2M flash】

ESP8266 启动日志中分区表的内容及其解释 分区表日志&#xff1a; I (136) boot: Partition Table: I (147) boot: ## Label Usage Type ST Offset Length I (170) boot: 0 nvs WiFi data 01 02 00009000 00004000 I (193) boot:…...

dl学习笔记:(7)完整神经网络流程

完整神经网络流程 反向传播链式求导 代码实现反向传播动量法Momentum开始迭代为什么选择小批量TensorDataset与DataLoader 反向传播 由于本节的公式比较多&#xff0c;所以如果哪里写错了漏写了&#xff0c;还请帮忙指出以便进行改正&#xff0c;谢谢。 在前面的章节已经介绍过…...

三分钟简单了解一些HTML的标签和语法_01

1.图片建议建立一个文件夹如下图所示 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"keywords"><title>魔神羽落</title><style>.testone{background-color: #ff53e…...

C# 中使用Hash用于密码加密

通过一定的哈希算法&#xff08;典型的有MD5&#xff0c;SHA-1等&#xff09;&#xff0c;将一段较长的数据映射为较短小的数据&#xff0c;这段小数据就是大数据的哈希值。他最大的特点就是唯一性&#xff0c;一旦大数据发生了变化&#xff0c;哪怕是一个微小的变化&#xff0…...

【C++高并发服务器WebServer】-2:exec函数簇、进程控制

本文目录 一、exec函数簇介绍二、exec函数簇 一、exec函数簇介绍 exec 函数族的作用是根据指定的文件名找到可执行文件&#xff0c;并用它来取代调用进程的内容&#xff0c;换句话说&#xff0c;就是在调用进程内部执行一个可执行文件。 exec函数族的函数执行成功后不会返回&…...

Linux将目录挂载到另一个目录,类似软硬链接,并通过fstab实现

格式 <源> <目> none bind 0 0例如 /data/var/lib/docker /var/lib/docker none bind 0 0参数使用制表符tab间隔 效果 rootDebian12:~# cat /etc/fstab | grep -v ^# /dev/mapper/Debian12--vg-root / ext4 erro…...

【C++模板】:如何判断自定义类型是否实现某个函数

一、引子 偶尔我们会面对这样的尴尬的场景&#xff0c;我们需要显示的去判断在某个自定义类型中&#xff0c;是否已经提供了我们期待的API接口&#xff0c;以避免产生“莫须有”的错误。阁下该如何破解此问题&#xff01; 这里&#xff0c;直接给出一种通用的方法&#xff0c;…...

关于CAN(FD)转以太网详细介绍

一、功能描述 CANFD 完全向下兼容 CAN &#xff0c;以下统称 CAN(FD) 。 SG-CAN(FD)NET-210 是一款用来把 CANFD 总线数据转为网口数据的设备。 网口支持 TCP Sever 、 TCP Client 、 UDP Sever 、 UDP Client 四种模式。 可以通过软件配置和 Web 网页配置。 两路…...

GPU算力平台|在GPU算力平台部署MedicalGPT医疗大模型的应用教程

文章目录 一、GPU算力服务平台云端GPU算力平台 二、平台账号注册流程MedicalGPT医疗大模型的部署MedicalGPT医疗大模型概述MedicalGPT部署步骤 一、GPU算力服务平台 云端GPU算力平台 云端GPU算力平台专为GPU加速计算设计&#xff0c;是一个高性能计算中心&#xff0c;广泛应用…...

【ChatGPT】意义空间与语义运动定律 —— AI 世界的神秘法则

作者介绍 斯蒂芬•沃尔弗拉姆&#xff08;Stephen Wolfram&#xff09; 计算机科学家、数学家和理论物理学家&#xff0c;当今科学和技术领域重要的革新者之一。他创造了在全世界备受推崇的软件系统——Mathematica、Wolfram|Alpha和Wolfram语言。 在 ChatGPT 内部&#xff0c;…...

Harbor 部署教程

Harbor 部署教程 一.背景二.遇到的问题及解决办法1.服务无法启动,相关的容器不断重启 三.操作步骤1. 创建工作目录并进入2. 配置 Docker 守护进程3. 重启 Docker 服务4. 下载 Harbor 离线安装包和 Docker Compose5. 安装 Docker Compose6. 解压 Harbor 安装包7. 配置 Harbor8. …...

hive 自动检测、自动重启、记录检测日志、自动清理日志

最终效果 定时检测hive运行状态&#xff0c;进程不存在或者进程存在但是不监听端口的hiveserver2&#xff0c;自动重新拉起每次检测脚本执行的日志都会保存在log目录下.check文件&#xff0c;每一个月一个文件每月15日&#xff0c;删除2月前的检测日志开启hive自带日志输出后&…...

C++类和对象下详细指南

C类和对象下详细指南 1. 初始化列表与构造函数 1.1 初始化列表概述 初始化列表在C中用于初始化对象的成员变量&#xff0c;特别是当你需要在对象构造时就明确成员变量的值时。通过初始化列表&#xff0c;成员变量的初始化可以在进入构造函数体之前完成。这不仅可以提升性能&…...

【算法C++】构造回文字符串问题

问题描述 小C手中有一个由小写字母组成的字符串 s。她希望构造另一个字符串 t&#xff0c;并且这个字符串需要满足以下几个条件&#xff1a; t 由小写字母组成&#xff0c;且长度与 s 相同。t 是回文字符串&#xff0c;即从左到右与从右到左读取相同。t 的字典序要小于 s&…...

基于java线程池和EasyExcel实现数据异步导入

基于java线程池和EasyExcel实现数据异步导入 2.代码实现 2.1 controller层 PostMapping("import")public void importExcel(MultipartFile file) throws IOException {importService.importExcelAsync(file);}2.2 service层 Resource private SalariesListener sa…...

使用Layout三行布局(SemiDesign)

tips&#xff1a;Semi Design网址 &#xff1a;Semi Design 1、安装Semi # 使用 npm npm i douyinfe/semi-ui# 使用 yarn yarn add douyinfe/semi-ui# 使用 pnpm pnpm add douyinfe/semi-ui 2、引入Layout布局 import { Layout } from douyinfe/semi-ui;3、开始实现三行布局…...

缓存之美:万文详解 Caffeine 实现原理(下)

上篇文章&#xff1a;缓存之美&#xff1a;万文详解 Caffeine 实现原理&#xff08;上&#xff09; getIfPresent 现在我们对 put 方法有了基本了解&#xff0c;现在我们继续深入 getIfPresent 方法&#xff1a; public class TestReadSourceCode {Testpublic void doRead() …...

PHP防伪溯源一体化管理系统小程序

&#x1f50d; 防伪溯源一体化管理系统&#xff0c;品质之光&#xff0c;根源之锁 &#x1f680; 引领防伪技术革命&#xff0c;重塑品牌信任基石 我们自豪地站在防伪技术的前沿&#xff0c;为您呈现基于ThinkPHP和Uniapp精心锻造的多平台&#xff08;微信小程序、H5网页&…...

STM32——LCD

一、引脚配置 查看引脚 将上述引脚都设置为GPIO_Output 二、导入驱动文件 将 LCD 驱动的 Inc 以及 Src 中的 fonts.h,lcd.h 和 lcd.c 导入到自己工程的驱动文件中。 当然&#xff0c;后面 lcd 的驱动学习可以和 IMX6U 一块学。 三、LCD函数 void LCD_Clear(u16 Color); 功能…...

洛谷刷题1-3

比较巧妙&#xff0c;求最小公倍数&#xff0c;看多少个数一次循环&#xff0c;直接求解就好了&#xff0c;N的数量级比较大&#xff0c;一层循环也会超时&#xff0c;也用了点双指针的想法&#xff08;归并排序&#xff09; 这里很大的问题&#xff0c;主要是cin输入的时候遇到…...

Facebook 元宇宙与全球文化交流的新趋势

随着科技的快速发展&#xff0c;虚拟现实与增强现实技术逐渐成为全球社交平台的重要组成部分。Facebook&#xff08;现改名为 Meta&#xff09;率先将目光投向了元宇宙这一新兴领域&#xff0c;致力于打造一个超越传统社交媒体的虚拟空间&#xff0c;成为全球文化交流的新平台。…...

结构化布线系统分为六个子系统

文章目录 前言系统介绍1. 工作区子系统 (Work Area Subsystem)2. 水平布线子系统 (Horizontal Cabling Subsystem)3. 管理子系统 (Administration Subsystem)4. 干线&#xff08;垂直&#xff09;子系统 (Backbone Cabling Subsystem)5. 设备间子系统 (Equipment Room Subsyste…...

亿坊软件前端命名规范

在前端开发中&#xff0c;文件命名的重要性不言而喻。由于历史原因和个人习惯&#xff0c;不同的开发者在命名DOM结构、图片和CSS文件时&#xff0c;可能会产生不一致的情况。这不仅会导致维护成本增加&#xff0c;还会降低团队协作效率。因此&#xff0c;制定一套统一的命名规…...

单片机-STM32 IIC通信(OLED屏幕)(十一)

一、屏幕的分类 1、LED屏幕&#xff1a; 由无数个发光的LED灯珠按照一定的顺序排列而成&#xff0c;当需要显示内容的时候&#xff0c;点亮相关的LED灯即可&#xff0c;市场占有率很高&#xff0c;主要是用于户外&#xff0c;广告屏幕&#xff0c;成本低。 LED屏是一种用发光…...

【Qt】: QPointer详解

考古&#xff1a; 《Qt智能指针之QScopedPointer》 QPointer是Qt框架中的一个智能指针类&#xff0c;用于安全地管理QObject派生对象的指针。它的主要功能是自动将指针置为nullptr&#xff0c;当它所指向的QObject对象被销毁时。这在Qt应用程序中非常有用&#xff0c;因为QObj…...

15_业务系统基类

创建脚本 SystemRoot.cs 因为 业务系统基类的子类 会涉及资源加载服务层ResSvc.cs 和 音乐播放服务层AudioSvc.cs 所以在业务系统基类 提取引用资源加载服务层ResSvc.cs 和 音乐播放服务层AudioSvc.cs 并调用单例初始化 using UnityEngine; // 功能 : 业务系统基类 public c…...

C++中explicit关键字的介绍和使用详解

某一天突然发现Qt自动生成的类文件的构造函数前边都有explicit关键字&#xff0c;但是explicit关键字什么意思呐&#xff1f; 在C中&#xff0c;explicit是一个关键字&#xff0c;主要用于修饰构造函数或转换运算符&#xff0c;其作用是防止隐式类型转换或隐式构造行为。 下面…...

动态内存管理

本章重点 1.为什么存在动态内存分配 2.动态内存函数的介绍 3.malloc free calloc realloc 4.常见的动态内存错误 一.为什么存在动态内存分配 二.动态内存函数的介绍 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include &…...

Java 中的各种锁详解

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/literature?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;…...

进制之间转换

「 一、十进制 二进制 」 1.十进制转二进制&#xff1a;一直除以2直到商为0&#xff0c;再反向取余数。 例&#xff1a;13&#xff08;十进制&#xff09;转1101&#xff08;二进制&#xff09; 2.二进制转十进制:最后一位数开始是2^0&#xff0c;然后一直按照指数递增的方式…...

微信小程序获取位置服务

wx.getLocation({type: gcj02,success(res) {wx.log(定位成功);},fail(err) {wx.log(定位失败, err);wx.showModal({content: 请打开手机和小程序中的定位服务,success: (modRes) > {if (modRes.confirm) {wx.openSetting({success(setRes) {if (setRes.authSetting[scope.u…...

数据结构——实验八·学生管理系统

嗨~~欢迎来到Tubishu的博客&#x1f338;如果你也是一名在校大学生&#xff0c;正在寻找各种编程资源&#xff0c;那么你就来对地方啦&#x1f31f; Tubishu是一名计算机本科生&#xff0c;会不定期整理和分享学习中的优质资源&#xff0c;希望能为你的编程之路添砖加瓦⭐&…...

Linux应用编程(五)USB应用开发-libusb库

一、基础知识 1. USB接口是什么&#xff1f; USB接口&#xff08;Universal Serial Bus&#xff09;是一种通用串行总线&#xff0c;广泛使用的接口标准&#xff0c;主要用于连接计算机与外围设备&#xff08;如键盘、鼠标、打印机、存储设备等&#xff09;之间的数据传输和电…...

Swift语言探索:Sequence与Collection的详细解读

在Swift编程语言中&#xff0c;Sequence和Collection是两个非常重要的协议&#xff0c;它们定义了遍历和访问元素集合的方式。理解这两个协议不仅有助于我们更好地掌握Swift的集合类型&#xff0c;还能让我们在编写代码时更加灵活和高效。本文将详细解读Sequence与Collection&a…...

解锁C# EF/EF Core:从入门到进阶的技术飞跃

一、EF/EF Core 初相识 在.NET 开发的广阔天地中&#xff0c;Entity Framework (EF) 及其轻量级、可扩展、跨平台的版本 Entity Framework Core (EF Core)&#xff0c;犹如两颗璀璨的明星&#xff0c;照亮了数据访问层开发的道路。它们是开源的对象关系映射器&#xff08;ORM&…...

大模型搜广推?对算法工作的影响

大模型与传统应用结合的性质 长期看是一种范式革新。算力和模型定义的边界发生变化&#xff0c;选择生成式AI或大模型发展方向时&#xff0c;会不断发现新的增长曲线&#xff0c;目前在不断被验证。 现阶段大模型确实带来了增量信息&#xff0c;但推荐过程仍在原有流程基础上…...