草庐IT

WPF开发快速入门【4】自定义控件与用户控件

Enjoy programming! 2023-03-28 原文

概述

本文描述WPF的自定义控件和用户控件。

 

自定义控件

前面文章介绍了WPF的ControlTemplate,当我们对系统控件自带的样式不太满意时,我们可以通过控件模板自定义用户的样式,以Button为例,我们可以设计一个圆形的按钮,并通过触发器控制一些动态效果。在使用控件模板时,我们通过TemplateBinding来引用控件的一些属性,这个属性的范围仅限于Button本身所拥有的属性。

如果我想设计一款带图片的按钮,通过控件模板就实现不了了,因为这个图片按钮的控件应该具备一个类似Image这样的属性,但Button控件没有这个属性,所以就实现不了我们想要的功能了。

这时候可以使用自定义控件来解决问题。

我们新建一个ImageButton的自定义控件,系统自动生成一个类文件和一个样式文件: 

//ImageButton.cs
    public class ImageButton : Control
    {
        static ImageButton()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(ImageButton), new FrameworkPropertyMetadata(typeof(ImageButton)));
        }
    }

//Generic.xaml
    <Style TargetType="{x:Type local:ImageButton}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:ImageButton}">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

 首先我们需要把ImageButton 父类修改为Button,表示这个控件功能继承于Button,然后我们为这个类增加一个Image属性

        public ImageSource Image
        {
            get { return (ImageSource)GetValue(ImageProperty); }
            set { SetValue(ImageProperty, value); }
        }

        public static readonly DependencyProperty ImageProperty =
            DependencyProperty.Register("Image", typeof(ImageSource), typeof(ImageButton), new PropertyMetadata(null));

此时,这个控件的后台代码就完成了。

然后我们再仔细看看Generic.xaml中对于local:ImageButton这个控件的样式描述,这不就是修改它的控件模板吗?不过此时,除了Button的现有属性,我们还多了一个Image属性可以使用。

下面我们完善一下这个控件模板的描述。

    <Style TargetType="{x:Type local:ImageButton}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:ImageButton}">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="Gray"
                            BorderThickness="1">
                        <StackPanel Orientation="Horizontal">
                            <Image Source="{TemplateBinding Image}"/>
                            <Label Content="{TemplateBinding Content}" VerticalAlignment="Center"/>
                        </StackPanel>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

 这样一个自定义控件就做好了。

 

 和控件模板一样,我们还是可以通过Trigger控制控件的一些动态效果:

    <Style TargetType="{x:Type local:ImageButton}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:ImageButton}">
                    <Border x:Name="border">
                        ...
                    </Border>                   
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter TargetName="border" Property="Background" Value="LightBlue" />
                            <Setter TargetName="border" Property="BorderBrush" Value="Gray" />
                            <Setter TargetName="image" Property="Margin" Value="2" />
                        </Trigger>
                        <Trigger Property="IsEnabled" Value="False">
                            <Setter TargetName="border" Property="Background" Value="LightGray" />
                            <Setter TargetName="image" Property="Opacity" Value="0.2" />
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

  

用户控件

用户控件比较简单,就是通过一些现有控件的组合,形成一个可以通用的控件。例如:通过组合一个加号的图片、一个减号的图片、一个文本框,我们可以组合一个NumericUpDown控件。 

<UserControl x:Class="LearnWPF.Controls.NumericUpDown">    
    <Border BorderThickness="1" BorderBrush="LightGray" >
        <Grid >
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="30"/>
                <ColumnDefinition Width="50"/>
                <ColumnDefinition Width="30"/>
            </Grid.ColumnDefinitions>
            <Border Grid.Column="0" >
                <Image Source="Images/Subtract.png" Margin="4"
                       MouseDown="ButtonSubtract_Click">
                </Image>
            </Border>
            <TextBox x:Name="txtValue" Text="1.220"                      
                     TextChanged="txtValue_TextChanged"/>
            <Border Grid.Column="2">
                <Image Source="Images/Add.png" Margin="4"
                       MouseDown="ButtonAdd_Click">
                </Image>
            </Border>
        </Grid>
    </Border>
</UserControl>

 我们可以给控件增加一个Value的属性,然后当用户点击图片按钮时,我们修改Value的值即可。

        private void ButtonAdd_Click(object sender, RoutedEventArgs e)
        {
            Value += Increment;           
        }

        private void ButtonSubtract_Click(object sender, RoutedEventArgs e)
        {
            Value -= Increment;            
        }

 此时,这个用户控件就开发完成了。但它不支持MVVM模式,因为Value属性不是依赖属性,我们需要把Value属性定义为依赖属性:

        public decimal Value
        {
            get { return (decimal)GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }

        public static readonly DependencyProperty ValueProperty =
               DependencyProperty.Register("Value", typeof(decimal), typeof(NumericUpDown), new UIPropertyMetadata(new decimal(), ValuePropertyChanged));

        private static void ValuePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs arg)
        {
            NumericUpDown control = obj as NumericUpDown;
            decimal Data = (decimal)arg.NewValue;
            control.txtValue.Text = Math.Round(Data, control.DecimalPlaces).ToString();
        }

 此时Value属性就支持MVVM,而且支持双向Binding模式

<xyc:NumericUpDown Value="{Binding Hour,Mode=TwoWay}" Minimum="0" Maximum="23"/>

 由于 Minimum和Maximum不是依赖属性,只能直接幅值,不能绑定。

 

自绘的用户控件

有时候我们需要做一些很奇怪的控件,如下:

这个也是通过用户控件实现。我的经验是:在用户控件里放一个Image控件,缩放模式设置为Fill,然后在后台画个图并复值给Image控件即可。

在绘图时,由于用户会调整控件尺寸,所以绘图控件的定位是比较麻烦的,我们可以画一个指定尺寸的图,然后让Image来处理缩放,这就比较简单了。

设计代码:

<UserControl x:Class="LearnWPF.Controls.Clock"
    <Grid>      
<Image x:Name="imageBitmap" Stretch="Fill" /> </Grid> </UserControl>

 后台代码:

    public partial class Clock : UserControl
    {
       
        public Clock()
        {
            InitializeComponent();
            this.Loaded += Clock_Loaded;
        }

        private void Clock_Loaded(object sender, RoutedEventArgs e)
        {
            DrawImage();
        }

        private void DrawImage()
        {
            DrawingGroup group = new DrawingGroup();
            using (DrawingContext ctx = group.Open())
            {
                DrawImageByContext(ctx);
            }
            group.Freeze();
            DrawingImage drawimage = new DrawingImage(group);
            this.imageBitmap.Source = drawimage;
        }

        private void DrawImageByContext(DrawingContext ctx)
        {
            int PicWidth = 500;
            int PicHeight = 500;          
            ctx.DrawRectangle(Brushes.Transparent, null, new Rect(0, 0, PicWidth, PicHeight));
            //拿着ctx尽情绘图吧           
        }        
    }

   

资源

系列目录:WPF开发快速入门【0】前言与目录 

代码下载:Learn WPF: WPF学习笔记 (gitee.com)

有关WPF开发快速入门【4】自定义控件与用户控件的更多相关文章

  1. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  2. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

    我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

  3. ruby-on-rails - 使用 rails 4 设计而不更新用户 - 2

    我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它​​不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数

  4. Ruby Sinatra 配置用于生产和开发 - 2

    我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm

  5. ruby - 是否可以覆盖 gemfile 进行本地开发? - 2

    我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI

  6. ruby - 在 Windows 机器上使用 Ruby 进行开发是否会适得其反? - 2

    这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby​​-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub

  7. ruby-on-rails - 在 Rails 开发环境中为 .ogv 文件设置 Mime 类型 - 2

    我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain

  8. ruby-on-rails - 简单的 Ruby on Rails 问题——如何将评论附加到用户和文章? - 2

    我意识到这可能是一个非常基本的问题,但我现在已经花了几天时间回过头来解决这个问题,但出于某种原因,Google就是没有帮助我。(我认为部分问题在于我是一个初学者,我不知道该问什么......)我也看过O'Reilly的RubyCookbook和RailsAPI,但我仍然停留在这个问题上.我找到了一些关于多态关系的信息,但它似乎不是我需要的(尽管如果我错了请告诉我)。我正在尝试调整MichaelHartl'stutorial创建一个包含用户、文章和评论的博客应用程序(不使用脚手架)。我希望评论既属于用户又属于文章。我的主要问题是:我不知道如何将当前文章的ID放入评论Controller。

  9. ruby - RVM "ERROR: Unable to checkout branch ."单用户 - 2

    我在新的Debian6VirtualBoxVM上安装RVM时遇到问题。我已经安装了所有需要的包并使用下载了安装脚本(curl-shttps://rvm.beginrescueend.com/install/rvm)>rvm,但以单个用户身份运行时bashrvm我收到以下错误消息:ERROR:Unabletocheckoutbranch.安装在这里停止,并且(据我所知)没有安装RVM的任何文件。如果我以root身份运行脚本(对于多用户安装),我会收到另一条消息:Successfullycheckedoutbranch''安装程序继续并指示成功,但未添加.rvm目录,甚至在修改我的.bas

  10. ruby-on-rails - 如何生成传递一些自定义参数的 `link_to` URL? - 2

    我正在使用RubyonRails3.0.9,我想生成一个传递一些自定义参数的link_toURL。也就是说,有一个articles_path(www.my_web_site_name.com/articles)我想生成如下内容:link_to'Samplelinktitle',...#HereIshouldimplementthecode#=>'http://www.my_web_site_name.com/articles?param1=value1¶m2=value2&...我如何编写link_to语句“alàRubyonRailsWay”以实现该目的?如果我想通过传递一些

随机推荐