|
引言
今天在做一个设置文件夹路径的功能,就是一个文本框,加个按钮,点击按钮,弹出 FolderBrowserDialog 再选择文件夹路径,简单做法,可以直接 StackPanel 横向放置一个 TextBox 和一个 Image Button,然后点击按钮在 后台代码中给 ViewModel 的 FilePath赋值。但是这样属实不够优雅,UI 不够优雅,代码实现也可谓是强耦合,那接下来我分享一下我的实现方案。
目标
做这个设置文件夹路径的功能,我的目标是点击任何地方都可以打开 FolderBrowserDialog,那就需要把文本框,按钮作为一个整体控件,且选择完文件夹路径后就给绑定的 ViewModel 的 FilePath 赋值。
准备工作
首先,既然要设计一个整体控件,那么 UI 如下:
接下来创建这个整体的控件,不使用 Button ,直接使用 Control,来创建自定义控件 OpenFolderBrowserControl :
Code Behind 代码如下:- public class OpenFolderBrowserControl : Control,
- {
- static OpenFolderBrowserControl()
- {
- DefaultStyleKeyProperty.OverrideMetadata(typeof(OpenFolderBrowserControl), new FrameworkPropertyMetadata(typeof(OpenFolderBrowserControl)));
- }
- public static readonly DependencyProperty FilePathProperty = DependencyProperty.Register("FilePath", typeof(string), typeof(OpenFolderBrowserControl));
- [Description("文件路径")]
- public string FilePath
- {
- get => (string)GetValue(FilePathProperty);
- set => SetValue(FilePathProperty, value);
- }
- }
复制代码 Themes/Generic.xaml 中的设计代码如下:- </TextBox.Style>
- </TextBox>
- <Border
- Height="56"
- Margin="-60,0,0,0"
- Background="White"
- BorderBrush="#CAD2DD"
- BorderThickness="2"
- CornerRadius="0,8,8,0">
- <StackPanel
- <Grid>
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="1*" />
- <ColumnDefinition Width="2*" />
- </Grid.ColumnDefinitions>
-
- <TextBlock
- HorizontalAlignment="Center"
- VerticalAlignment="Center"
- FontSize="22"
- Text="设置文件路径:" />
- <local:OpenFolderBrowserControl
- Grid.Column="1"
- HorizontalAlignment="Left"
- Click="OpenFolderBrowserControl_Click"
- Command="{Binding ClickCommand}"
- FilePath="{Binding FilePath, Mode=TwoWay}" />
- </Grid>HorizontalAlignment="Center"
- <Grid>
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="1*" />
- <ColumnDefinition Width="2*" />
- </Grid.ColumnDefinitions>
-
- <TextBlock
- HorizontalAlignment="Center"
- VerticalAlignment="Center"
- FontSize="22"
- Text="设置文件路径:" />
- <local:OpenFolderBrowserControl
- Grid.Column="1"
- HorizontalAlignment="Left"
- Click="OpenFolderBrowserControl_Click"
- Command="{Binding ClickCommand}"
- FilePath="{Binding FilePath, Mode=TwoWay}" />
- </Grid>VerticalAlignment="Center"
- <Grid>
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="1*" />
- <ColumnDefinition Width="2*" />
- </Grid.ColumnDefinitions>
-
- <TextBlock
- HorizontalAlignment="Center"
- VerticalAlignment="Center"
- FontSize="22"
- Text="设置文件路径:" />
- <local:OpenFolderBrowserControl
- Grid.Column="1"
- HorizontalAlignment="Left"
- Click="OpenFolderBrowserControl_Click"
- Command="{Binding ClickCommand}"
- FilePath="{Binding FilePath, Mode=TwoWay}" />
- </Grid>Orientation="Horizontal">
- <Grid>
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="1*" />
- <ColumnDefinition Width="2*" />
- </Grid.ColumnDefinitions>
-
- <TextBlock
- HorizontalAlignment="Center"
- VerticalAlignment="Center"
- FontSize="22"
- Text="设置文件路径:" />
- <local:OpenFolderBrowserControl
- Grid.Column="1"
- HorizontalAlignment="Left"
- Click="OpenFolderBrowserControl_Click"
- Command="{Binding ClickCommand}"
- FilePath="{Binding FilePath, Mode=TwoWay}" />
- </Grid><Ellipse
- <Grid>
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="1*" />
- <ColumnDefinition Width="2*" />
- </Grid.ColumnDefinitions>
-
- <TextBlock
- HorizontalAlignment="Center"
- VerticalAlignment="Center"
- FontSize="22"
- Text="设置文件路径:" />
- <local:OpenFolderBrowserControl
- Grid.Column="1"
- HorizontalAlignment="Left"
- Click="OpenFolderBrowserControl_Click"
- Command="{Binding ClickCommand}"
- FilePath="{Binding FilePath, Mode=TwoWay}" />
- </Grid> Width="5"
- <Grid>
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="1*" />
- <ColumnDefinition Width="2*" />
- </Grid.ColumnDefinitions>
-
- <TextBlock
- HorizontalAlignment="Center"
- VerticalAlignment="Center"
- FontSize="22"
- Text="设置文件路径:" />
- <local:OpenFolderBrowserControl
- Grid.Column="1"
- HorizontalAlignment="Left"
- Click="OpenFolderBrowserControl_Click"
- Command="{Binding ClickCommand}"
- FilePath="{Binding FilePath, Mode=TwoWay}" />
- </Grid> Height="5"
- <Grid>
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="1*" />
- <ColumnDefinition Width="2*" />
- </Grid.ColumnDefinitions>
-
- <TextBlock
- HorizontalAlignment="Center"
- VerticalAlignment="Center"
- FontSize="22"
- Text="设置文件路径:" />
- <local:OpenFolderBrowserControl
- Grid.Column="1"
- HorizontalAlignment="Left"
- Click="OpenFolderBrowserControl_Click"
- Command="{Binding ClickCommand}"
- FilePath="{Binding FilePath, Mode=TwoWay}" />
- </Grid> Margin="3"
- <Grid>
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="1*" />
- <ColumnDefinition Width="2*" />
- </Grid.ColumnDefinitions>
-
- <TextBlock
- HorizontalAlignment="Center"
- VerticalAlignment="Center"
- FontSize="22"
- Text="设置文件路径:" />
- <local:OpenFolderBrowserControl
- Grid.Column="1"
- HorizontalAlignment="Left"
- Click="OpenFolderBrowserControl_Click"
- Command="{Binding ClickCommand}"
- FilePath="{Binding FilePath, Mode=TwoWay}" />
- </Grid> Fill="#949494" />
- <Grid>
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="1*" />
- <ColumnDefinition Width="2*" />
- </Grid.ColumnDefinitions>
-
- <TextBlock
- HorizontalAlignment="Center"
- VerticalAlignment="Center"
- FontSize="22"
- Text="设置文件路径:" />
- <local:OpenFolderBrowserControl
- Grid.Column="1"
- HorizontalAlignment="Left"
- Click="OpenFolderBrowserControl_Click"
- Command="{Binding ClickCommand}"
- FilePath="{Binding FilePath, Mode=TwoWay}" />
- </Grid><Ellipse
- <Grid>
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="1*" />
- <ColumnDefinition Width="2*" />
- </Grid.ColumnDefinitions>
-
- <TextBlock
- HorizontalAlignment="Center"
- VerticalAlignment="Center"
- FontSize="22"
- Text="设置文件路径:" />
- <local:OpenFolderBrowserControl
- Grid.Column="1"
- HorizontalAlignment="Left"
- Click="OpenFolderBrowserControl_Click"
- Command="{Binding ClickCommand}"
- FilePath="{Binding FilePath, Mode=TwoWay}" />
- </Grid> Width="5"
- <Grid>
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="1*" />
- <ColumnDefinition Width="2*" />
- </Grid.ColumnDefinitions>
-
- <TextBlock
- HorizontalAlignment="Center"
- VerticalAlignment="Center"
- FontSize="22"
- Text="设置文件路径:" />
- <local:OpenFolderBrowserControl
- Grid.Column="1"
- HorizontalAlignment="Left"
- Click="OpenFolderBrowserControl_Click"
- Command="{Binding ClickCommand}"
- FilePath="{Binding FilePath, Mode=TwoWay}" />
- </Grid> Height="5"
- <Grid>
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="1*" />
- <ColumnDefinition Width="2*" />
- </Grid.ColumnDefinitions>
-
- <TextBlock
- HorizontalAlignment="Center"
- VerticalAlignment="Center"
- FontSize="22"
- Text="设置文件路径:" />
- <local:OpenFolderBrowserControl
- Grid.Column="1"
- HorizontalAlignment="Left"
- Click="OpenFolderBrowserControl_Click"
- Command="{Binding ClickCommand}"
- FilePath="{Binding FilePath, Mode=TwoWay}" />
- </Grid> Margin="3"
- <Grid>
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="1*" />
- <ColumnDefinition Width="2*" />
- </Grid.ColumnDefinitions>
-
- <TextBlock
- HorizontalAlignment="Center"
- VerticalAlignment="Center"
- FontSize="22"
- Text="设置文件路径:" />
- <local:OpenFolderBrowserControl
- Grid.Column="1"
- HorizontalAlignment="Left"
- Click="OpenFolderBrowserControl_Click"
- Command="{Binding ClickCommand}"
- FilePath="{Binding FilePath, Mode=TwoWay}" />
- </Grid> Fill="#949494" />
- <Grid>
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="1*" />
- <ColumnDefinition Width="2*" />
- </Grid.ColumnDefinitions>
-
- <TextBlock
- HorizontalAlignment="Center"
- VerticalAlignment="Center"
- FontSize="22"
- Text="设置文件路径:" />
- <local:OpenFolderBrowserControl
- Grid.Column="1"
- HorizontalAlignment="Left"
- Click="OpenFolderBrowserControl_Click"
- Command="{Binding ClickCommand}"
- FilePath="{Binding FilePath, Mode=TwoWay}" />
- </Grid><Ellipse
- <Grid>
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="1*" />
- <ColumnDefinition Width="2*" />
- </Grid.ColumnDefinitions>
-
- <TextBlock
- HorizontalAlignment="Center"
- VerticalAlignment="Center"
- FontSize="22"
- Text="设置文件路径:" />
- <local:OpenFolderBrowserControl
- Grid.Column="1"
- HorizontalAlignment="Left"
- Click="OpenFolderBrowserControl_Click"
- Command="{Binding ClickCommand}"
- FilePath="{Binding FilePath, Mode=TwoWay}" />
- </Grid> Width="5"
- <Grid>
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="1*" />
- <ColumnDefinition Width="2*" />
- </Grid.ColumnDefinitions>
-
- <TextBlock
- HorizontalAlignment="Center"
- VerticalAlignment="Center"
- FontSize="22"
- Text="设置文件路径:" />
- <local:OpenFolderBrowserControl
- Grid.Column="1"
- HorizontalAlignment="Left"
- Click="OpenFolderBrowserControl_Click"
- Command="{Binding ClickCommand}"
- FilePath="{Binding FilePath, Mode=TwoWay}" />
- </Grid> Height="5"
- <Grid>
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="1*" />
- <ColumnDefinition Width="2*" />
- </Grid.ColumnDefinitions>
-
- <TextBlock
- HorizontalAlignment="Center"
- VerticalAlignment="Center"
- FontSize="22"
- Text="设置文件路径:" />
- <local:OpenFolderBrowserControl
- Grid.Column="1"
- HorizontalAlignment="Left"
- Click="OpenFolderBrowserControl_Click"
- Command="{Binding ClickCommand}"
- FilePath="{Binding FilePath, Mode=TwoWay}" />
- </Grid> Margin="3"
- <Grid>
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="1*" />
- <ColumnDefinition Width="2*" />
- </Grid.ColumnDefinitions>
-
- <TextBlock
- HorizontalAlignment="Center"
- VerticalAlignment="Center"
- FontSize="22"
- Text="设置文件路径:" />
- <local:OpenFolderBrowserControl
- Grid.Column="1"
- HorizontalAlignment="Left"
- Click="OpenFolderBrowserControl_Click"
- Command="{Binding ClickCommand}"
- FilePath="{Binding FilePath, Mode=TwoWay}" />
- </Grid> Fill="#949494" />
- </StackPanel>
- </Border>
- </StackPanel>
- </Border>
- </ControlTemplate>
- </Setter.Value>
- </Setter>
- </Style>
复制代码 这样创建的控件实际上是没有点击功能的。
那么接下来看一下点击功能方案实现。
点击功能方案实现
因为有 MVVM 的存在,所以在 WPF 中 Button 点击功能有两种方案,
- 第一种是直接注册点击事件,比如 Click="OpenFolderBrowserControl_Click"
- 第二种是绑定Command、CommandParameter、CommandTarget,比如 Command="{Binding ClickCommand}" CommandParameter="" CommandTarget="" 。
但是上文中我们定义的是一个 Control ,它既没有 Click 也没有 Command,所以,我们需要给 OpenFolderBrowserControl 定义Click 和 Command。
定义点击事件
定义点击事件比较简单,直接声明一个 RoutedEventHandler ,命名为 Click 就可以了。- public event RoutedEventHandler? Click;
复制代码 定义Command
定义 Command 就需要 ICommandSource 接口,重点介绍一下 ICommandSource 接口。
ICommandSource 接口用于指示控件可以生成和执行命令。该接口定义了三个成员
- 定义了一个 ICommand 类型的属性 Command,
- 定义了一个表示与控件关联的, IInputElement 类型的 CommandTarget
- 定义了一个表示命令参数,object 类型的属性 CommandParameter
上述两段的定义如下:- public class OpenFolderBrowserControl : Control, ICommandSource
- {
- //上文中已有代码此处省略...
- #region 定义点击事件
- public event RoutedEventHandler? Click;
- #endregion
- #region 定义command
- public static readonly DependencyProperty CommandProperty =
- DependencyProperty.Register("Command", typeof(ICommand), typeof(OpenFolderBrowserControl), new UIPropertyMetadata(null))
- public ICommand Command
- {
- get { return (ICommand)GetValue(CommandProperty); }
- set { SetValue(CommandProperty, value); }
- }
- public object CommandParameter
- {
- get { return (object)GetValue(CommandParameterProperty); }
- set { SetValue(CommandParameterProperty, value); }
- }
- public static readonly DependencyProperty CommandParameterProperty =
- DependencyProperty.Register("CommandParameter", typeof(object), typeof(OpenFolderBrowserControl));
- public IInputElement CommandTarget
- {
- get { return (IInputElement)GetValue(CommandTargetProperty); }
- set { SetValue(CommandTargetProperty, value); }
- }
- public static readonly DependencyProperty CommandTargetProperty =
- DependencyProperty.Register("CommandTarget", typeof(IInputElement), typeof(OpenFolderBrowserControl));
复制代码 实现点击功能
好了,到此为止我仅定义好了点击事件和 Command,但是并没有能够触发这两个功能的地方。
既然是要实现点击功能,那最直观的方法就是 OnMouseLeftButtonUp,该方法是 WPF 核心基类 UIElement的虚方法,我们可以直接重写。如下代码:- public class OpenFolderBrowserControl : Control, ICommandSource
- {
- //上文中已有代码此处省略...
-
- protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
- {
- base.OnMouseLeftButtonUp(e);
- //调用点击事件
- Click?.Invoke(e.Source, e);
- //调用Command
- ICommand command = Command;
- object parameter = CommandParameter;
- IInputElement target = CommandTarget;
- RoutedCommand routedCmd = command as RoutedCommand;
- if (routedCmd != null && routedCmd.CanExecute(parameter, target))
- {
- routedCmd.Execute(parameter, target);
- }
- else if (command != null && command.CanExecute(parameter))
- {
- command.Execute(parameter);
- }
- }
- }
复制代码 到此位置,我们的非Button自定义控件实现点击的需求就完成了,接下来测试一下。
测试
准备测试窗体和 ViewModel,这里为了不引入依赖包,也算是复习一下 MVVM 的实现,就手动实现 ICommand 和 INotifyPropertyChanged。
ICommand 实现:- public class RelayCommand : ICommand
- {
- private readonly Action? _execute;
- public RelayCommand(Action? execute)
- {
- _execute = execute;
- }
- public bool CanExecute(object? parameter)
- {
- return true;
- }
- public void Execute(object? parameter)
- {
- _execute?.Invoke();
- }
- public event EventHandler? CanExecuteChanged;
- }
复制代码 TestViewModel 实现:
这里的 ClickCommand 触发之后,我输出了当前 FilePath的值。- public class TestViewModel : INotifyPropertyChanged
- {
- public TestViewModel()
- {
- FilePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
- }
- public event PropertyChangedEventHandler? PropertyChanged;
-
- protected virtual void OnPropertyChanged(string propertyName)
- {
- PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
- }
- private string filePath = string.Empty;
- /// <summary>
- /// 文件路径
- /// </summary>
- public string FilePath
- {
- get { return filePath; }
- set { filePath = value; OnPropertyChanged(nameof(FilePath)); }
- }
- private ICommand clickCommand = null;
- /// <summary>
- /// 点击事件
- /// </summary>
- public ICommand ClickCommand
- {
- get { return clickCommand ??= new RelayCommand(Click); }
- set { clickCommand = value; }
- }
- private void Click()
- {
- MessageBox.Show($"ViewModel Clicked!The value of FilePath is {FilePath}");
- }
- }
复制代码 窗体UI代码:- <Grid>
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="1*" />
- <ColumnDefinition Width="2*" />
- </Grid.ColumnDefinitions>
-
- <TextBlock
- HorizontalAlignment="Center"
- VerticalAlignment="Center"
- FontSize="22"
- Text="设置文件路径:" />
- <local:OpenFolderBrowserControl
- Grid.Column="1"
- HorizontalAlignment="Left"
- Click="OpenFolderBrowserControl_Click"
- Command="{Binding ClickCommand}"
- FilePath="{Binding FilePath, Mode=TwoWay}" />
- </Grid>
复制代码 窗体 Code Behind 代码- public partial class MainWindow : Window
- {
- public MainWindow()
- {
- InitializeComponent();
- DataContext = new TestViewModel();
- }
- private void OpenFolderBrowserControl_Click(object sender, RoutedEventArgs e)
- {
- FolderBrowserDialog folderBrowserDialog = new FolderBrowserDialog();
- DialogResult result = folderBrowserDialog.ShowDialog();
- if (result == System.Windows.Forms.DialogResult.OK)
- {
- string selectedFolderPath = folderBrowserDialog.SelectedPath;
- var Target = sender as OpenFolderBrowserControl;
- if (Target != null)
- {
- Target.FilePath = selectedFolderPath;
- }
- }
- }
- }
复制代码 测试结果
我点击整个控件的任意地方,都能打开文件夹浏览器。
选择音乐文件夹后,弹窗提示 ViewModel Clicked!The value of FilePath is C:\Users\Administrator\Music
结论
从测试结果中可以看出,在 UI 注册的 Click 和 Command 均触发。这个方案仅仅是抛砖引玉,只要任意控件(非button)需要实现点击功能,都可以这样去实现。
实现核心就是两个方案:
- 直接定义点击事件。
- 实现ICommandSource。
然后再重写各种鼠标事件,鼠标按下,鼠标抬起,双击等都可以实现。
上述方案既保证了 UI 的优雅也保证了 MVVM 架构的前后分离特性。
如果大家有更好更优雅的方案,欢迎留言讨论。
来源:https://www.cnblogs.com/pandefu/archive/2023/08/17/17638683.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|