前言
WPF中事件的绑定和解绑放在什么生命周期中比较合适呢?
窗口
在 WPF 中,窗口(Window)是一种特殊的控件,其生命周期也包括了一系列的事件,你可以在这些事件中进行事件的绑定和解绑。
生命周期
OnInitialized => Loaded => Closing => Closed
以下是一些比较合理的时机:
Loaded 事件: 当窗口加载完成并准备好与用户交互时,可以在 Loaded 事件中进行事件绑定。这是一个非常常用的时机。
1 2 3 4 5
| private void Window_Loaded(object sender, RoutedEventArgs e) { myButton.Click += MyButton_Click; }
|
Closing 事件: 当用户关闭窗口时,可以在 Closing 事件中进行事件解绑。这是释放资源和进行清理操作的好时机。
1 2 3 4 5
| private void Window_Closing(object sender, CancelEventArgs e) { myButton.Click -= MyButton_Click; }
|
OnInitialized 方法: 在窗口初始化时进行事件绑定也是一种合理的方式。
1 2 3 4 5 6 7
| protected override void OnInitialized(EventArgs e) { base.OnInitialized(e);
myButton.Click += MyButton_Click; }
|
Closed 事件: 当窗口已经完全关闭并且将要从视图中移除时,可以在 Closed 事件中进行一些清理工作。
1 2 3 4
| private void Window_Closed(object sender, EventArgs e) { }
|
总的来说,绑定和解绑事件的时机取决于你的具体需求和情况。
通常来说:
Loaded 时绑定事件,因为窗口已经准备好与用户交互。
Closing 时解绑事件,因为你可以在窗口关闭前进行一些资源释放和清理工作。
控件
在 WPF(Windows Presentation Foundation)开发中,通常在控件的生命周期方法中进行事件的绑定和解绑是比较合理的。
基本
以下是一些常用的生命周期方法,你可以考虑在这些方法中进行事件的绑定和解绑:
Loaded 事件: 控件已经被加载到 Visual 树中,可以安全地进行事件绑定。
在 Loaded 事件中进行事件绑定是比较常见的做法,因为此时控件已经准备好与用户交互。
1 2 3 4 5
| private void MyControl_Loaded(object sender, RoutedEventArgs e) { myButton.Click += MyButton_Click; }
|
Unloaded 事件: 控件即将从 Visual 树中移除,适合在此时进行事件解绑。
1 2 3 4 5
| private void MyControl_Unloaded(object sender, RoutedEventArgs e) { myButton.Click -= MyButton_Click; }
|
MVVM
OnDetaching 方法 (MVVM 模式): 如果你使用了 MVVM 模式,你可以在自定义的附加行为中实现事件的绑定和解绑逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class MyButtonBehavior : Behavior<Button> { protected override void OnAttached() { base.OnAttached();
AssociatedObject.Click += MyButton_Click; }
protected override void OnDetaching() { base.OnDetaching();
AssociatedObject.Click -= MyButton_Click; } }
|
应用
应用退出
应用退出的监听
1 2 3 4 5 6 7 8 9
| protected override void OnExit(ExitEventArgs e) { ZAppDataHelper.SaveData("" + ZHttpUtil.isSt); Console.WriteLine(@"应用退出"); base.OnExit(e); Environment.Exit(0); }
|
应用退出的方式
1
| Application.Current.Shutdown();
|
和
注意其它界面退出应用
不要使用Environment.Exit(0);!
不要使用Environment.Exit(0);!
不要使用Environment.Exit(0);!
这样OnExit中监听不到应用的退出。
启动监听异常退出
异常退出的处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
| protected override void OnStartup(StartupEventArgs e) { ZLogHelper.InitLog4Net(); RegisterEvents(); base.OnStartup(e); }
private void RegisterEvents() { TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
DispatcherUnhandledException += App_DispatcherUnhandledException;
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; }
private static void TaskScheduler_UnobservedTaskException ( object sender, UnobservedTaskExceptionEventArgs e ) { try { if (e.Exception is Exception exception) { HandleException(exception); } } catch (Exception ex) { HandleException(ex); } finally { e.SetObserved(); } }
private static void CurrentDomain_UnhandledException ( object sender, UnhandledExceptionEventArgs e ) { try { if (e.ExceptionObject is Exception exception) { HandleException(exception); } } catch (Exception ex) { HandleException(ex); } }
private static void App_DispatcherUnhandledException ( object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e ) { try { HandleException(e.Exception); } catch (Exception ex) { HandleException(ex); } finally { e.Handled = true; } }
private static void HandleException(Exception ex) { ZLogHelper.Logerror.Error(ex.Message); Current.Shutdown(); }
|
防止应用多开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| [DllImport("User32.dll")] private static extern int FindWindow ( string className, string windowName );
[DllImport("user32.dll")] public static extern bool SetForegroundWindow(int hWnd);
private static void CheckProcess() { Console.WriteLine(@"程序启动"); Process current = Process.GetCurrentProcess(); string strProcessName = Process.GetCurrentProcess().ProcessName; var processList = Process.GetProcessesByName(strProcessName); if (processList.Length > 1) { MessageWindow.Show("当前程序已在运行,请勿重复运行。"); foreach (var process in processList) { if (process.Id == current.Id) continue; int hWnd = FindWindow( null, process.MainWindowTitle ); SetForegroundWindow(hWnd); } Environment.Exit(1); } }
protected override void OnStartup(StartupEventArgs e) { CheckProcess(); base.OnStartup(e); }
|
自动释放内存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
public static void CrackerOnlyGC(int sleepSpan = 30) { void ThreadStart(object s) { while (true) try { GC.Collect(); GC.WaitForPendingFinalizers(); Thread.Sleep(TimeSpan.FromSeconds(sleepSpan)); } catch (Exception) { } } new Thread(ThreadStart) { IsBackground = true }.Start(); }
protected override void OnStartup(StartupEventArgs e) { CrackerOnlyGC(); base.OnStartup(e); }
|
内存泄漏示例
使用Lambda绑定事件
有一些特殊情况可能会导致内存泄漏:Lambda 表达式中的引用捕获。
如果在 Lambda 表达式中捕获了外部的引用(比如一个对象实例),而这个 Lambda 表达式又被长时间保持,这可能会导致对象无法被垃圾回收,从而间接导致内存泄漏。
1 2 3 4 5 6 7 8 9 10 11
| public class Example { private SomeClass _someObject;
public Example() { _someObject = new SomeClass();
myButton.Click += (sender, e) => _someObject.DoSomething(); } }
|
在这个例子中,Lambda 表达式捕获了外部的 _someObject 引用。
如果 Example 对象一直存在并持有对 myButton.Click 事件的订阅,那么 _someObject 将无法被垃圾回收,直到 Example 对象被释放。
为了避免这种情况,可以在不需要订阅事件时,取消订阅以释放对对象的引用,或者使用弱事件(Weak Event)模式来管理事件订阅,以确保对象可以被垃圾回收。