C# WPF MVVM 实战 – 5- 用绑定,通过 VM 设置 View 的控件焦点

本文介绍了在MVVM模式下,如何使用ViewModel控制视图的焦点。提供了两种方法:使用DataTrigger设置FocusManager.FocusedElement和使用AttachedProperty实现控件属性绑定。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文链接:http://www.cnblogs.com/leptonation/p/3292961.html

本文介绍在 MVVM 中,如何用 ViewModel 控制焦点。

这焦点设置个东西嘛,有些争论。就是到底要不要用 ViewModel 来控制视图的键盘输入焦点。这里不讨论,假设你就是要通过 VM,设置输入焦点在哪里。

MSDN 有解释关于 Focus 的,还有 FocusManager,点击这里打开。不知道的话建议你先看看,只求结果的可以直接看下面代码,抄就是了。这次,初级的解释全部略过,直接说做法,看不懂的请留言。做法很多,大概两种比较符合 MVVM 模式:

1. DataTrigger 设置 FocusedElement

Style 中写 Trigger,DataTrigger,值变化触发设置 FocusManager 的 FocusedElement。

XAML:

<Grid>
	<Grid.Style>
		<Style>
			<Style.Triggers>
				<DataTrigger Binding="{Binding Path=ScanFtBarCodeNow}" Value="True">
					<Setter Property="FocusManager.FocusedElement" Value="{Binding ElementName=FtNoTextBox}"/>
				</DataTrigger>
		    </Style.Triggers>
		</Style>
	</Grid.Style>
	<TextBox name="FtNoTextBox"/>
</Grid>
注意:放置 Style 的位置要注意。一层又一层 Grid 的话,FocusManager 单单设置 FocusedElement 为 TextBox 不足够。原因在 MSDN 有说明,不重复。

2. Attached Property

使用 Attached Property 为控件加属性,值变化触发 CallBack,然后 UIElement.Focus()。所谓值变化,比如,bool 变为 True 。

CODE:

publicstaticclass FocusBehavior {
    publicstaticbool GetIsFocused(DependencyObject obj) {
        return (bool)obj.GetValue(IsFocusedProperty);
    }
    publicstaticvoid SetIsFocused(DependencyObject obj, bool value) {
        obj.SetValue(IsFocusedProperty, value);
    }
    publicstaticreadonly DependencyProperty IsFocusedProperty =
            DependencyProperty.RegisterAttached(
            "IsFocused", 
            typeof(bool),
            typeof(FocusBehavior),
            new UIPropertyMetadata(false, (s, e) => {
                var sender = (UIElement)s;
                if ((bool)e.NewValue) {
                    sender.Focus();
                    Keyboard.Focus(sender);
                }
                })
            );
}
XAML:
<TextBox local:FocusBehavior.IsFocused="{Binding Path=ScanBarCodeNow}"/>

这做法虽然简单,但实际绑定的并非 UIElement.IsFocused 这只读属性,看CallBack 代码就知道,两个Focus() 都只是方法。界面操作焦点离开此控件时候,DependencyProperty 的值不会变化。后果是,设置一次 true 后,属性值 true,焦点离开它依然是 true,你再设置它 true,会被认定为值无变化,CallBack 没有执行,Focus() 不会运行。Workaround:先设 false 再设 true。

好傻吧。改一下,虽然 IsFocus 是只读无法直接绑定去改它,但可以把 GotFocus 和 LostFocus 事件接上此 Attached Property,触发变更 DependencyProperty 的值,使它变成双向绑定。

using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;

namespace Lepton_Practical_MVVM_5 {
    publicstaticclass FocusBehavior {
        privatestatic Dictionary<UIElement, RoutedEventHandler> handlers =new Dictionary<UIElement, RoutedEventHandler>();
        publicstaticbool? GetIsFocused(DependencyObject obj) {
            return (bool?)obj.GetValue(IsFocusedProperty);
        }
        publicstaticvoid SetIsFocused(DependencyObject obj, bool? value) {
            obj.SetValue(IsFocusedProperty, value);
        }
        publicstaticreadonly DependencyProperty IsFocusedProperty =
                DependencyProperty.RegisterAttached(
                "IsFocused",
                typeof(bool?),
                typeof(FocusBehavior),
                new UIPropertyMetadata() {
                    DefaultValue =null,
                    PropertyChangedCallback =
                        (s, e) => {
                            UIElement sender = (UIElement)s;
                            RoutedEventHandler x;
                            if (!handlers.TryGetValue(sender, out x)) {
                                Attach(sender);
                            }
                            if ((bool)e.NewValue) {
                                sender.Focus();
                                Keyboard.Focus(sender);
                            }
                        }
                });
        privatestaticvoid Attach(UIElement sender) {
            RoutedEventHandler handler = (s, e) => {
                UIElement ui = (UIElement)s;
                if (e.RoutedEvent == UIElement.GotFocusEvent) {
                    ui.SetValue(IsFocusedProperty, true);
                }
                if (e.RoutedEvent == UIElement.LostFocusEvent) {
                    ui.SetValue(IsFocusedProperty, false);
                }
            };
            sender.GotFocus += handler;
            sender.LostFocus += handler;
            handlers.Add(sender, handler);
        }
    }
}

Dictionary 为了记下已连接的 Handler ,以免重复加入。

XAML:

<Window x:Class="Lepton_Practical_MVVM_5.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Lepton_Practical_MVVM_5"
        Title="MainWindow" Height="350" Width="525">
		<Grid>
			<TextBox local:FocusBehavior.IsFocused="{Binding Path=IsTextBox1Focus,Mode=TwoWay}" Height="23" HorizontalAlignment="Left" Margin="49,54,0,0" Name="textBox1" VerticalAlignment="Top" Width="120"/>
		</Grid>
</Window>
源代码下载


<think>我们正在讨论使用C#WPFMVVMLight框架实现界面绑定的完整流程。根据提供的引用,我们可以总结出以下步骤:1.**安装MvvmLight**:首先需要安装MvvmLight库。可以通过NuGet包管理器安装。2.**创建模型(Model)**:模型类通常包含数据属性,例如引用[3]中的`MainModel`。3.**创建视图模型(ViewModel)**:视图模型继承自`ViewModelBase`,并包含属性(绑定到视图)和命令(绑定到用户操作)。在属性设置器中调用`RaisePropertyChanged`方法通知视图更新(如引用[1]和[3]所示)。4.**设置ViewModelLocator**:通常有一个ViewModelLocator类,用于提供视图模型的实例。在App.xaml中注册ViewModelLocator(引用[1]中提到了Main属性在ViewModelLocator中)。5.**视图(View)绑定**:在XAML中,将视图的数据上下文绑定到对应的视图模型(可以通过ViewModelLocator或直接设置DataContext)。然后使用绑定语法将控件属性绑定到视图模型的属性或命令(如引用[4]中的ContentControl绑定)。6.**命令绑定**:在视图模型中创建`RelayCommand`(或`RelayCommand<T>`)类型的属性,并在构造函数中初始化,指定执行的方法(如引用[3]中的`ShowCommand`)。在视图中,将按钮的Command属性绑定到该命令。下面我们详细说明每个步骤:###1.安装MvvmLight使用NuGet安装`MvvmLightLibs`(注意:现在可能是`MvvmLight`或`MvvmLightLibsStd10`等,根据.NET版本选择)。###2.创建模型(Model)模型类包含应用程序的数据。例如:```csharppublicclassMainModel{publicintCount{get;set;}}```###3.创建视图模型(ViewModel)视图模型继承`ViewModelBase`,包含属性和命令。属性在设置时调用`RaisePropertyChanged`(或Set方法)通知视图更新。例如:```csharpusingGalaSoft.MvvmLight;usingGalaSoft.MvvmLight.Command;publicclassMainViewModel:ViewModelBase{privateMainModelmainModel;publicMainModelMainModel{get{returnmainModel;}set{Set(refmainModel,value);}//使用Set方法自动触发PropertyChanged}publicRelayCommandAddCommand{get;privateset;}publicMainViewModel(){MainModel=newMainModel{Count=0};AddCommand=newRelayCommand(Add);}privatevoidAdd(){MainModel.Count++;//注意:由于MainModel.Count是值类型,直接修改不会触发PropertyChanged,因此需要通知MainModel属性变化或使用其他方式//如果需要,可以在这里调用RaisePropertyChanged(nameof(MainModel)),但更好的做法是让MainModel实现INotifyPropertyChanged}}```注意:如果模型中的属性变化需要通知视图,那么模型也应该实现`INotifyPropertyChanged`,或者将模型属性拆分成视图模型中的简单属性。###4.设置ViewModelLocatorViewModelLocator是一个集中管理视图模型实例的类。通常在App.xaml中注册它。首先,创建ViewModelLocator类:```csharppublicclassViewModelLocator{publicMainViewModelMain=>newMainViewModel();//或者使用IoC容器管理实例}```然后在App.xaml中声明:```xaml<Application...xmlns:local="clr-namespace:YourNamespace"><Application.Resources><ResourceDictionary><local:ViewModelLocatorx:Key="Locator"/></ResourceDictionary></Application.Resources></Application>```在视图中设置数据上下文:```xaml<Window...DataContext="{BindingSource={StaticResourceLocator},Path=Main}">```###5.视图绑定在XAML中,使用绑定语法将控件属性绑定到视图模型的属性。例如:```xaml<TextBlockText="{BindingMainModel.Count}"/><ButtonContent="增加"Command="{BindingAddCommand}"/>```###6.命令绑定在视图模型中创建命令(如`RelayCommand`),并在视图中将按钮的Command属性绑定到该命令。###完整示例以下是一个简单的完整示例:**Model:**```csharppublicclassMainModel{publicintCount{get;set;}}```**ViewModel:**```csharpusingGalaSoft.MvvmLight;usingGalaSoft.MvvmLight.Command;publicclassMainViewModel:ViewModelBase{privateMainModel_mainModel;publicMainModelMainModel{get=>_mainModel;set=>Set(ref_mainModel,value);}publicRelayCommandAddCommand{get;}publicMainViewModel(){MainModel=newMainModel{Count=0};AddCommand=newRelayCommand(ExecuteAdd);}privatevoidExecuteAdd(){MainModel.Count++;//由于MainModel.Count是值类型,修改后需要通知MainModel属性已更新RaisePropertyChanged(nameof(MainModel));//更好的做法是让MainModel也实现INotifyPropertyChanged,然后在MainModel中触发属性更改事件。}}```**View(MainWindow.xaml):**```xaml<Windowx:Class="YourNamespace.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"DataContext="{BindingSource={StaticResourceLocator},Path=Main}"><StackPanel><TextBlockText="{BindingMainModel.Count}"/><ButtonContent="增加"Command="{BindingAddCommand}"/></StackPanel></Window>```**注意:**为了让`ViewModelLocator`生效,需要在App.xaml中声明,如前所述。###补充:模型实现INotifyPropertyChanged为了在模型属性变化时自动更新视图,可以让模型实现`INotifyPropertyChanged`接口,或者在视图模型中将模型的属性暴露为视图模型的属性(并传递属性更改通知)。这里展示让模型实现`INotifyPropertyChanged`:```csharppublicclassMainModel:ObservableObject//或者实现INotifyPropertyChanged,但ViewModelBase已经实现了,所以可以直接继承{privateint_count;publicintCount{get=>_count;set=>Set(ref_count,value);}}```这样,在视图模型中修改`MainModel.Count`时,由于`MainModel`的`Count`属性已经会触发`PropertyChanged`事件,所以视图会自动更新。###总结使用MVVMLight实现界面绑定的完整流程包括:1.安装MvvmLight库2.创建模型(Model)3.创建视图模型(ViewModel),继承`ViewModelBase`,定义属性和命令4.设置ViewModelLocator,并在App.xaml中注册5.在视图中设置数据上下文,并绑定属性和命令通过以上步骤,可以实现WPF应用程序的MVVM模式。§§相关问题§§1.MvvmLight中如何实现消息传递(Messenger)?2.在MvvmLight中如何实现依赖注入?3.如何实现MvvmLight中的事件绑定(如鼠标事件)?4.MvvmLight中的ViewModelBase和ObservableObject有什么区别?</think>### C# WPF MVVMLight 界面绑定完整流程 以下是使用 MVVMLight 实现 WPF 界面绑定的完整步骤: #### 1. **安装 MVVMLight** ```bash # NuGet 安装命令 Install-Package MvvmLightLibsStd10 ``` #### 2. **创建模型类 (Model)** ```csharp // MainModel.cs public class MainModel : ObservableObject { private int _count; public int Count { get => _count; set => Set(ref _count, value); // 自动触发属性通知 } } ``` #### 3. **创建视图模型 (ViewModel)** ```csharp // MainViewModel.cs public class MainViewModel : ViewModelBase { private MainModel _model; public MainModel Model { get => _model; set => Set(ref _model, value); } public RelayCommand AddCommand { get; } public MainViewModel() { Model = new MainModel { Count = 0 }; AddCommand = new RelayCommand(ExecuteAdd); } private void ExecuteAdd() { Model.Count++; // 自动更新UI } } ``` #### 4. **配置 ViewModelLocator** ```xml <!-- App.xaml --> <Application.Resources> <ResourceDictionary> <vm:ViewModelLocator x:Key="Locator" /> </ResourceDictionary> </Application.Resources> ``` ```csharp // ViewModelLocator.cs public class ViewModelLocator { public MainViewModel Main => new MainViewModel(); } ``` #### 5. **视图绑定 (View)** ```xml <!-- MainWindow.xaml --> <Window DataContext="{Binding Source={StaticResource Locator}, Path=Main}"> <StackPanel> <TextBlock Text="{Binding Model.Count}" /> <Button Content="增加" Command="{Binding AddCommand}" /> </StackPanel> </Window> ``` #### 6. **关键机制说明** 1. **数据绑定**: - `Text="{Binding Model.Count}"` 实现数据到UI的自动同步 - `Set(ref _field, value)` 触发 `INotifyPropertyChanged` 通知 2. **命令绑定**: - `RelayCommand` 将按钮点击映射到 `ExecuteAdd` 方法 - 无需后台代码处理事件 3. **资源定位**: - `ViewModelLocator` 统一管理ViewModel实例[^2] - 通过 `StaticResource` 实现松耦合 #### 7. **运行效果** - 点击按钮时 `Count` 值自动增加 - 界面实时更新显示新数值 - 完全分离业务逻辑与UI代码 ### 最佳实践建议 1. **属性通知**: - 所有绑定属性必须调用 `Set()` 或 `RaisePropertyChanged()` 2. **命令参数**: - 使用 `RelayCommand<T>` 处理带参数的命令 3. **数据验证**: - 在 `setter` 中添加验证逻辑 4. **跨页面通信**: - 使用 `Messenger` 组件实现ViewModel间通信[^4] > 完整示例参考:[MvvmLight官方文档](http://www.mvvmlight.net) [^2][^3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值