WPF --- 非Button自定义控件实现点击功能

这篇具有很好参考价值的文章主要介绍了WPF --- 非Button自定义控件实现点击功能。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

引言

今天在做一个设置文件夹路径的功能,就是一个文本框,加个按钮,点击按钮,弹出 FolderBrowserDialog 再选择文件夹路径,简单做法,可以直接 StackPanel 横向放置一个 TextBox 和一个 Image Button,然后点击按钮在 后台代码中给 ViewModelFilePath赋值。但是这样属实不够优雅,UI 不够优雅,代码实现也可谓是强耦合,那接下来我分享一下我的实现方案。

目标

做这个设置文件夹路径的功能,我的目标是点击任何地方都可以打开 FolderBrowserDialog,那就需要把文本框,按钮作为一个整体控件,且选择完文件夹路径后就给绑定的 ViewModelFilePath 赋值。

准备工作

首先,既然要设计一个整体控件,那么 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 中的设计代码如下:

<Style TargetType="{x:Type local:OpenFolderBrowserControl}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:OpenFolderBrowserControl}">
                    <Border
                        Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                        <StackPanel Orientation="Horizontal">

                            <TextBox
                                Width="{TemplateBinding Width}"
                                Height="56"
                                Padding="0,0,60,0"
                                IsEnabled="False"
                                IsReadOnly="True"
                                Text="{Binding FilePath, RelativeSource={RelativeSource Mode=TemplatedParent}}">
                                <TextBox.Style>
                                    <Style TargetType="{x:Type TextBox}">
                                        <Setter Property="Background" Value="White" />
                                        <Setter Property="BorderBrush" Value="#CAD2DD" />
                                        <Setter Property="Foreground" Value="#313F56" />
                                        <Setter Property="BorderThickness" Value="2" />
                                        <Setter Property="KeyboardNavigation.TabNavigation" Value="None" />
                                        <Setter Property="HorizontalContentAlignment" Value="Left" />
                                        <Setter Property="FocusVisualStyle" Value="{x:Null}" />
                                        <Setter Property="AllowDrop" Value="False" />
                                        <Setter Property="FontSize" Value="22" />
                                        <Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst" />
                                        <Setter Property="Stylus.IsFlicksEnabled" Value="False" />
                                        <Setter Property="HorizontalAlignment" Value="Left" />
                                        <Setter Property="VerticalAlignment" Value="Center" />
                                        <Setter Property="Margin" Value="20,0,0,0" />
                                        <Setter Property="Template">
                                            <Setter.Value>
                                                <ControlTemplate TargetType="{x:Type TextBox}">
                                                    <Border
                                                        x:Name="border"
                                                        Background="{TemplateBinding Background}"
                                                        BorderBrush="{TemplateBinding BorderBrush}"
                                                        BorderThickness="{TemplateBinding BorderThickness}"
                                                        CornerRadius="8"
                                                        SnapsToDevicePixels="True">
                                                        <Grid>
                                                            <ScrollViewer
                                                                x:Name="PART_ContentHost"
                                                                Margin="20,0,0,0"
                                                                VerticalAlignment="{TemplateBinding VerticalAlignment}"
                                                                VerticalContentAlignment="Center"
                                                                Focusable="False"
                                                                FontFamily="{TemplateBinding FontFamily}"
                                                                FontSize="{TemplateBinding FontSize}"
                                                                HorizontalScrollBarVisibility="Hidden"
                                                                VerticalScrollBarVisibility="Hidden" />
                                                            <TextBlock
                                                                x:Name="WARKTEXT"
                                                                Margin="20,0,0,0"
                                                                HorizontalAlignment="Left"
                                                                VerticalAlignment="Center"
                                                                FontFamily="{TemplateBinding FontFamily}"
                                                                FontSize="{TemplateBinding FontSize}"
                                                                Foreground="#A0ADBE"
                                                                Text="{TemplateBinding Tag}"
                                                                Visibility="Collapsed" />
                                                        </Grid>
                                                    </Border>
                                                    <ControlTemplate.Triggers>
                                                        <Trigger Property="IsEnabled" Value="False">
                                                            <Setter TargetName="border" Property="Opacity" Value="0.56" />
                                                        </Trigger>
                                                        <Trigger Property="IsMouseOver" Value="True">
                                                            <Setter TargetName="border" Property="BorderBrush" Value="#CAD2DD" />
                                                        </Trigger>
                                                        <Trigger Property="IsKeyboardFocused" Value="True">
                                                            <Setter TargetName="border" Property="BorderBrush" Value="#CAD2DD" />
                                                        </Trigger>
                                                        <MultiTrigger>
                                                            <MultiTrigger.Conditions>
                                                                <Condition Property="Text" Value="" />
                                                                <!--<Condition Property="IsFocused" Value="False"/>-->
                                                            </MultiTrigger.Conditions>
                                                            <Setter TargetName="WARKTEXT" Property="Visibility" Value="Visible" />
                                                        </MultiTrigger>
                                                    </ControlTemplate.Triggers>
                                                </ControlTemplate>
                                            </Setter.Value>
                                        </Setter>
                                    </Style>
                                </TextBox.Style>
                            </TextBox>
                            <Border
                                Height="56"
                                Margin="-60,0,0,0"
                                Background="White"
                                BorderBrush="#CAD2DD"
                                BorderThickness="2"
                                CornerRadius="0,8,8,0">
                                <StackPanel
                                    HorizontalAlignment="Center"
                                    VerticalAlignment="Center"
                                    Orientation="Horizontal">
                                    <Ellipse
                                        Width="5"
                                        Height="5"
                                        Margin="3"
                                        Fill="#949494" />
                                    <Ellipse
                                        Width="5"
                                        Height="5"
                                        Margin="3"
                                        Fill="#949494" />
                                    <Ellipse
                                        Width="5"
                                        Height="5"
                                        Margin="3"
                                        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 定义ClickCommand

定义点击事件

定义点击事件比较简单,直接声明一个 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 的实现,就手动实现 ICommandINotifyPropertyChanged

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 注册的 ClickCommand 均触发。这个方案仅仅是抛砖引玉,只要任意控件(非button)需要实现点击功能,都可以这样去实现。

实现核心就是两个方案:

  • 直接定义点击事件。
  • 实现ICommandSource。

然后再重写各种鼠标事件,鼠标按下,鼠标抬起,双击等都可以实现。

上述方案既保证了 UI 的优雅也保证了 MVVM 架构的前后分离特性。

如果大家有更好更优雅的方案,欢迎留言讨论。文章来源地址https://www.toymoban.com/news/detail-653522.html

到了这里,关于WPF --- 非Button自定义控件实现点击功能的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C# wpf 实现任意控件(包括窗口)更多调整大小功能

    第一节 Grid内控件拖动调整大小 第二节 Canvas内控件拖动调整大小 第三节 窗口拖动调整大小 第四节 附加属性实现拖动调整大小 第五章 拓展更多调整大小功能(本章) 上一章我们已经实现了任意控件统一的拖动调整功能,能够方便的给任意控件设置拖动调整大小。开发过程

    2024年01月18日
    浏览(30)
  • VUE中使用ElementUI组件的单选按钮el-radio-button实现第二点击时取消选择的功能

    页面样式为: html 代码为: js代码为:(记得在data中声明loglevel:\\\"\\\")

    2024年02月15日
    浏览(32)
  • unity,给button添加点击功能的办法

    以下是使用代码添加按钮功能的一个简单例子: 创建一个空对象,并添加一个Button组件,命名为“MyButton”。 创建一个脚本文件,命名为“ButtonManager”。 在ButtonManager脚本中添加以下代码: 将ButtonManager脚本挂载到MyButton对象上。 运行游戏,点击MyButton按钮,控制台输出“按

    2024年02月16日
    浏览(25)
  • excel功能区(ribbonx)编程笔记--2 button控件与checkbox控件

    我们上一章简单先了解了ribbonx的基本内容,以及使用举例实现自己修改ribbox的内容,本章紧接上一章,先讲解一下ribbonx的button控件。 在功能区的按钮中,可以使用内置图像或提供自已的图像,可以指定大按钮或者更小的形式,添加少量的代码甚至可以同时提供标签。此外,

    2024年02月11日
    浏览(26)
  • Android开发控件形状之自定义圆角button(三种形态)

    第一步:在drawable文件下创建button的形状描述文件btn_shape.xml btn1.xml btn2.xml btn3.xml 第二步:在布局文件中layout.xml中对btn1.xml以上三种其中一种的引用语句,用来设置button形状: android:background=\\\"@drawable/btn1\\\" 效果图: 点击前 点击后     解析shape文件中的android:shape属性:  Android

    2024年02月13日
    浏览(29)
  • WPF中用户控件和自定义控件

    无论是在WPF中还是WinForm中,都有用户控件(UserControl)和自定义控件(CustomControl),这两种控件都是对已有控件的封装,实现功能重用。但是两者还是有一些区别,本文对这两种控件进行讲解。 用户控件 注重复合控件的使用,也就是多个现有控件组成一个可复用的控件组

    2024年01月21日
    浏览(30)
  • WPF自定义按钮控件

    在平时的WPF应用中,系统提供的按钮控件确实可以实现正常的逻辑,但是从视觉方面看的话,确实不够美观,而且每个项目的UI设计不尽相同。那么自定义按钮控件就是必须的了,网上查找了很多自定义按钮控件的办法,但每次都是写到一半就报错。在参考了多个技术贴之后

    2024年02月08日
    浏览(31)
  • WPF自定义控件

    方式一:基于现有控件进行扩展,如基于button进行扩展,UI可直接用xmal进行编辑设计,逻辑用xaml.cs进行编辑 方法二:直接创建wpf自定义控件 本文用方法二开展自定义控件!!! 1.自定义控件的内容在代码cs文件中,自定义控件继承自Control,ui界面可在Genric.xaml中定义。 2.在

    2024年02月11日
    浏览(24)
  • wpf 自定义combox控件

    关键步骤 1、新建usercontrol使用基本的控件进行设计 2、依赖属性的定义,目的:外部调用时候能够使用属性进行控件样式的控制 例如 Width=\\\"200\\\" DisplayMemberPath=\\\"Name\\\" SelectedItem=\\\"{Binding SelectedItem,Mode=TwoWay}\\\" SelectionChanged=\\\"{Binding ProjectSelectCommand}\\\" CommandParameter=\\\"{Binding ElementName = Projec

    2024年02月09日
    浏览(30)
  • vue3自定义按钮点击变颜色实现(多选功能)

    实现效果图: 默认选中第一个按钮,未选中按钮为粉色,点击时颜色变为红色 利用动态类名,当定义isChange数值和下标index相同时,赋予act类名,实现变色效果

    2024年01月19日
    浏览(30)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包