×

wpf自定义控件教程

wpf自定义控件教程(如何自定义一个wpf控件并且用于windows窗口程序中)

admin admin 发表于2023-03-24 16:35:53 浏览35 评论0

抢沙发发表评论

本文目录

如何自定义一个wpf控件并且用于windows窗口程序中


有空的话就帮你写了,我以前做过在wpf中使用winform中的控件,其间也看到了在winform中使用wpf控件的文章,不过费点劲。不知道你是不是这个意思。
如果你说的windows窗口也是wpf写的话那用delegate和event就可以把任何你想要的东西以参数形式回传给父窗体,父窗体在new完了注册事件就行了。

C# wpf 如何实现自定义控件,布局时,大小发生变化,内部绘制的曲线跟随变化


public class TextBoxEx : TextBox { static TextBoxEx() { DefaultStyleKeyProperty.OverrideMetadata(typeof(TextBoxEx), new FrameworkPropertyMetadata(typeof(TextBoxEx))); HelpTextProperty = DependencyProperty.Register(“HelpText“, typeof(string), typeof(TextBoxEx), new FrameworkPropertyMetadata(“请您输入..“, new PropertyChangedCallback(OnHelpTextChanged))); } public static DependencyProperty HelpTextProperty; public string HelpText { get { return (string)GetValue(HelpTextProperty); } set { SetValue(HelpTextProperty, value); } } private static void OnHelpTextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { } }

wpf 自定义控件(CustomControl),用户控件(userControl)的区别及继承方法


窗体继承用usercontrol好些啊,customCcontrol主要是用来继承单独控件的,如combobox,datagrid这些的。customCcontrol修改控件外观的方式是可以访问控件本身template的里的控件,然后可以对其修改样式和增加逻辑。而楼主主要是想继承窗体,窗体是由N多个控件组成的。所以就应该用userControl。

WPF用户自定义控件的添加


B既然是个窗体,那就必须实例化,就是通过new来创建。但B窗体应该是已经显示出来了,所以应该把B窗体的实例直接传给A窗体。
从你的描述分析,我认为B是主窗体,A是后出来的,这样B在调用A.Show或者A.ShowDialog是可以再括号里加上this作为参数,那么在A里直接使用this.parent就可以取到B的实例了。
直接调用B.add()并不是没有反应,而是你添加了控件,但没有添加到你看到的那个B上,而是A自己作成的B。

WPF新手之将如何将一个成员变量或自定义类绑定到控件


①绑定到某个控件的依赖属性DependencyProperty ②绑定到定义了INotifyPropertyChanged的类(ObservableCollection就是实现了该接口) 先看DependencyProperty。可以把任何一个CLR对象绑定为DependencyProperty。在VS2010下输入propdp,按Tab,会生成一个模板public int MyProperty { get { return (int)GetValue(MyPropertyProperty); } set { SetValue(MyPropertyProperty, value); } } // Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc... public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register(“MyProperty“, typeof(int), typeof(ownerclass), new UIPropertyMetadata(0));MyProperty就是你要绑定的成员,修改它之后按Tab,后面的自动都会改变,如:public string Test { get { return (string)GetValue(TestProperty); } set { SetValue(TestProperty, value); } } // Using a DependencyProperty as the backing store for Test. This enables animation, styling, binding, etc... public static readonly DependencyProperty TestProperty = DependencyProperty.Register(“Test“, typeof(string), typeof(ownerclass), new UIPropertyMetadata(0));这里的ownerclass是拥有此成员的类名(如:class ownerclass {//...})。最后一个参数VS自动生成的有问题,因为它放入了一个参数0。实际上应该是UIPropertyMetadata(Object, PropertyChangedCallback, CoerceValueCallback, Boolean),用于设置UI中的数据更改后回调的函数。一般删除之或者用默认构造函数就行。如果需要它,一个简单的定义如下: public static readonly DependencyProperty IsNetworkChangedProperty = DependencyProperty.Register(“IsNetworkChanged“, typeof(bool), typeof(MainWindow), new UIPropertyMetadata(false, new PropertyChangedCallback(MainWindow.OnIsNetworkChanged))); private static void OnIsNetworkChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { //MessageBox.Show(d.GetType().ToString()); }注意这里是静态函数,由DependencyObject d来得到具体的通知对象。 然后在控件中进行绑定,如《TextBlock Text=“{Binding ElementName=MainWin, Path=Test}“/》,对Test变量的使用一如普通变量。 注意:①使用DependencyProperty比INotifyPropertyChanged的方法性能要高。因为它用Hash实现,不需要反射,而且是WPF系统中相当底层的一个基类 ②DependencyObjects are not marked as serializable ③The DependencyObject class overrides and seals the Equals() and GetHashCode() methods ④A DependencyObject has thread affinity –it can only be accessed on the thread on which it was created。(这一点很重要,特别是当程序中用到注册事件时,因为这些往往要开新线程。比如我的这个软件中,将bool型IsNetworkAvailabe用DependencyProperty绑定到控件,然后注册事件NetworkAvailabilityChanged,在NetworkAvailabilityChangedEventHandler中更新IsNetworkAvailabe值,这样就会抛出异常)在需要多线程 中操作的绑定变量,则需要用INotifyPropertyChanged再看如何用INotifyPropertyChanged:这里要将待绑定的变量包装在一个类中: class MyTest : INotifyPropertyChanged { private string test; public string Test { get { return this.test; } set { if (this.test != value) { this.test = value; this.NotifyPropertyChanged(“Test“); } } } public event PropertyChangedEventHandler PropertyChanged; private void NotifyPropertyChanged(String info) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(info)); } } } 其中test就是我们要绑定的变量。(绑定一个变量写这么多一堆,有点不值,我另开一篇,做一个snippet模板,用来快速生成)。 绑定时不知为何上面的绑定方法不灵了,于是换成代码绑定吧: MyTest Test; public MainWindow() { InitializeComponent(); this.Test = new MyTest(); Test.test = “thy“; TestBox.DataContext = this.Test; } 最终,在我的应用场景中,这两者都没能有用,我注册了事件NetworkAvailabilityChanged,在其中去修改一个绑定到ListBox的ObservableCollection,让其实现UI自动更新。但是总是会引起异常,我想可能是跨线程的原因吧。于是我试着用DependencyProperty绑定一个变量,在NetworkAvailabilityChanged事件中修改,还是不行,INotifyPropertyChanged也一样,BackgroundWorker也不行。最终没辙,在MainWindow中开了一个DispatcherTimer来轮询一个普通的bool变量: private DispatcherTimer _timer = new DispatcherTimer(DispatcherPriority.ContextIdle); _timer.Interval = TimeSpan.FromMilliseconds(100); _timer.Tick += new EventHandler(_timer_Tick); _timer.Start(); 虽说比较丑,但没法子啊,没这么多精力再去整了,先让它工作起来吧。

wpf自定义控件,自定义触发事件,比如,我自定义的控件中有个button按钮!见详细说明


  // 定义事件属性
  public static readonly RoutedEvent ClickRoutedEvent = EventManager.RegisterRoutedEvent(“Click“, RoutingStrategy.Bubble, typeof(ClickEventHandler), typeof(ControlName));
  
  [Description(“点击时发生“)]
        public event ClickEventHandler Click
        {
            add { AddHandler(ClickRoutedEvent, value); }
            remove { RemoveHandler(ClickRoutedEvent, value); }
        }
//触发点击事件,这里注册你控件上的那个点击按钮事件
void button_Click(object sender, RoutedEventArgs e)
{
    
            e.RoutedEvent = ScrollChangedRoutedEvent;
            e.Source = this;
            this.RaiseEvent(e);
}

WPF初学如何自定义WPF布局面板


  
  WPF布局引擎采用了一种递归的方式来实现控件及其子控件的布局,大致过程是这样的:要实现控件A的布局,那么先要实现A的子控件a1,a2,a3...的布局,要实现a1的布局,那么得实现a1的子控件a11,a12,a13...的布局,如此循环,然后但子控件的布局完成后,在完成父控件的布局,最后递归回去,递归结束,布局才算完成.
  控件的最终大小和位置是由该控件和父控件相磋商来完成的,父控件先给出其能给予子控件的力所能及的布局空间,子控件在反馈给父控件一个自己的期望值,父控件最后根据自己所拥有的空间大小与子控件的期望值分配一定的空间给子控件并返回自己的大小.这一系列过程是通过重写IC交易网面板的MeasureOverride和ArrangeOverride方法来完成的.
  
2,Size MeasureOverride(Size availableSize)方法
  该方法为布局中控件所需要的空间大小进行评估。
  参考如下代码:
以下是引用片段:
  protectedoverrideSizeMeasureOverride(SizeavailableSize)  {  SizechildrenSize=newSize(0,0);    foreach(UIElementchildinthis.Children)  {  child.Measure(newSize(Double.PositiveInfinity,Double.PositiveInfinity));
  childrenSize.Width+=child.DesiredSize.Width;
  childrenSize.Height+=child.DesiredSize.Height;  }    returnchildrenSize;}  在MeasureOverride阶段我们对面板中的每个Child调用了其Measure(Size sz)方法,该方法的作用是父控件告诉子控件其预计要分配给子控件的空间大小,对Child调用了该Measure方法后,子控件会在其内部给父控件一个回应以便告诉父控件它所期望的大小,而子控件的该期望值便保存在其child.DesiredSize中.如果Measure方法中传入的是正无穷大(new Size(Double.PositiveInfinity,Double.PositiveInfinity))时,相当于父控件在对子控件说“尽管说出你所需要的大小吧,如果可以的话全世界都可以给你“.
  MeasureOverrice方法中的availableSize参数正是其父控件(你编写的面板控件以后在实际使用时其父控件)对其调用Measure方法时传入的值(减去一些边界值,比如要减掉Margin等)
  MeasureOverride方法的返回值正是其告诉其父控件的期望值,即是其父控件(你编写的面板控件以后在实际使用时其父控件)对其调用Measure方法后,其DesiredSize值  注意:作为父控件,你可以很慈爱地仅可能地满足子控件的需要(尽管不一定有这能力),所以可以在对子控件调用Measure方法时传入一个正无穷大的尺寸,但作为子控件,你不能贪婪地向父控件索要正无穷大的空间,所以我们不能将正无穷大作为MeasureOverride方法的返回值,也不能直接将该方法的availableSize参数作为返回值(因为你的父控件有可能将正无穷大作为该参数传递给你)  
3,Size ArrangeOverride(Size finalSize)方法
  该方法作用在于为面板子控件提供布局空间即排列子控件并返回自身大小
  参考下面的代码:
以下是引用片段:
  protectedoverrideSizeArrangeOverride(SizefinalSize)  {  PointchildPos=newPoint(0,0);    foreach(UIElementchildinthis.Children)  {  child.Arrange(newRect(childPos,newSize(child.DesiredSize.Width,finalSize.Height)));
  childPos.X+=child.RenderSize.Width;  }    returnfinalSize;  }  该方法对每个Child调用Arrange方法,Arrange方法中传入的Rect结构告诉子控件其被安排在那个IC交易网空间内进行布局,然后子元素会根据自己得到的空间以及自己的对齐属性(XXXAlignment)进行放置,放置完成后子控件的大小等便得到了确定,而该值便存放在子控件的RenderSize中(即是ActualWidth与ActualHeight).
  控件本身也可以根据子控件占用的实际空间大小来决定自己的大小,也可以直接将得到的空间大小(即是ArrangeOverride方法中的finalSize参数)作为自己的最终大小返回
  ArrangeOverride方法中的finalSize参数是该控件的父控件(你编写的面板控件以后在实际使用时其父控件)在对该控件调用Arrange方法时传入的Rect的大小,正如该控件对其子控件调用Arrange方法一样.

WPF的自定义控件怎样添加Click事件


首先要给控件添加属性,然后为其添加事件,最后添加命令,具体步骤如下:

1,为控件添加属性(依赖属性,DependencyProperty)
正如下面的代码所示:

public static readonly DependencyProperty TimeProperty = 
            DependencyProperty.Register(“Time“, typeof(DateTime), typeof(ClockUserCtrl), 
            new FrameworkPropertyMetadata(DateTime.Now,new PropertyChangedCallback(TimePropertyChangedCallback)));

我们为控件(或者任何一个WPF类)添加的依赖属性都是“公开的“,“静态的“,“只读的“,其命名方式是“属性名+Property“,这是依赖属性一成不变的书写方式.对于依赖属性的注册可以在声明该属性时就调用DependencyProperty.Register()方法注册,也可以在其静态构造方法中注册.上面的DependencyProperty.Register方法的几个参数分别是:属性名(该属性名与声明的依赖属性名称“XXXProperty“相比仅仅是少了“Property“后缀,其它完全一样,否则在运行时会报异常),属性的数据类型,属性的拥有者的类型,元数据.
关于参数中传递的元数据:如果是普通的类则应该传递PropertyMetadata,如果是FrameworkElement则可以传递FrameworkPropertyMetadata,其中FrameworkPropertyMetadata中可以制定一些标记表明该属性发生变化时控件应该做出什么反应,比如某属性的变化会影响到该控件的绘制,那么就应该像这样书写该属性的元数据: new
FrameworkPropertyMetadata(defauleValue,
FrameworkPropertyMetadataOptions.AffectsRender);这样当该属性发生变化时系统会考虑重绘该控件.另外元数据中还保护很多内容,比如默认值,数据验证,数据变化时的回调函数,是否参与属性“继承“等.
然后,我们将该依赖属性包装成普通属性:

[Description(“获取或设置当前日期和时间“)]
        [Category(“Common Properties“)]
        public DateTime Time
        {
            get
            {
                return (DateTime)this.GetValue(TimeProperty);
            }
            set
            {
                this.SetValue(TimeProperty, value);
            }
        }

GetValue和SetValue方法来自于DependencyObject类,其用于获取或设置类的某属性值.
注意:在将依赖属性包装成普通属性时,在get和set块中除了按部就班的调用GetValue和SetValue方法外,不要进行任何其它的操作.下面的代码是不恰当的:

   [Description(“获取或设置当前日期和时间“)]
        [Category(“Common Properties“)]
        public DateTime Time
        {
            get
            {
                return (DateTime)this.GetValue(TimeProperty);
            }
            set
            {
                this.SetValue(TimeProperty, value);
                this.OnTimeUpdated(value);//Error
            }
        }

在以前这或许是很多人的惯用写法,但在WPF中,这样的写法存在潜在的错误,原因如下:我们知道继承于DependencyObject的类拥有GetValue和SetValue方法来获取或设置属性值,那为什么我们不直接使用该方法来获取或设置属性值,而要将其包装成普通的.NET属性呢,事实上在这里两种方式都是可以的,只不过包装成普通的.NET属性更符合.NET开发人员的习惯,使用GetValue和SetValue更像JAVA开发人员的习惯,但XAML在执行时似乎于JAVA开发人员一样,其不会调用.NET属性而是直接使用GetValue或SetValue方法,这样一来,我们写在get块和set块中的其它代码根本不会被XAML执行到.所以说,就上面的Time属性而言,C#(或其它)对该属性的调用不会出现任何问题,但该属性被用在XAML中时(比如在XAML对该属性进行数据绑定等),其set块中的this.OnTimeUpdated(value);语句不会被执行到.
那么,当Time属性发生变化时的确需要调用this.OnTimeUpdated(value);语句(因为该语句会引发时间被更新了的事件),还是在传递的依赖属性元数据做文章:
new FrameworkPropertyMetadata(DateTime.Now,new PropertyChangedCallback(TimePropertyChangedCallback)),我们为属性的变化指定了一个回调函数,当该属性变化时该回调函数就会被执行:

  private static void TimePropertyChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs arg)
        {
            if (sender != null && sender is ClockUserCtrl)
            {
                ClockUserCtrl clock = sender as ClockUserCtrl;
                clock.OnTimeUpdated((DateTime)arg.OldValue, (DateTime)arg.NewValue);
                
            }
        }


2,为控件添加事件(传阅事件,RoutedEvent)
添加传阅事件的方法与添加依赖属性的方法很类似:

  public static readonly RoutedEvent TimeUpdatedEvent = 
            EventManager.RegisterRoutedEvent(“TimeUpdated“,
             RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler《DateTime》), typeof(ClockUserCtrl));

其支持方法EventManager.RegisterRoutedEvent()对应的几个参数分别为:事件名称,事件传阅的方式(向上传阅,向下传阅或不传阅),事件对应的EventHandler的类型,事件拥有者的类型)
然后将事件包装成普通的.NET事件:

 [Description(“日期或时间被更新后发生“)]
        public event RoutedPropertyChangedEventHandler《DateTime》 TimeUpdated
        {
            add
            {
                this.AddHandler(TimeUpdatedEvent, value);
            }
            remove
            {
                this.RemoveHandler(TimeUpdatedEvent, value);
            }
        }

注意,与依赖属性一样,不要在add与remove块中添加除AddHandler与RemoveHandler以外的代码.
题外话,事件参数中的e.Handled=true并不是终止事件的传阅,这只是为事件做一个标记而已,以便在默认情况下的让那些事件处理函数在该标记为true的情况下不被调用,要为该标记为true的事件注册处理方法并让该方法得到执行,请使用AddHandler方法,并把最后一个参数handlerEventsToo设置为true,如下:

this.myInkCanvas.AddHandler(
      InkCanvas.MouseLeftButtonDownEvent,
      new MouseButtonEventHandler(
          myInkCanvas_MouseLeftButtonDown),
      true);
private void myInkCanvas_MouseLeftButtonDown(
       object sender, MouseButtonEventArgs e)
{
       //do something
}

然后编写惯用的OnXXX方法:

  protected virtual void OnTimeUpdated(DateTime oldValue, DateTime newValue)
        {
            RoutedPropertyChangedEventArgs《DateTime》 arg = 
                new RoutedPropertyChangedEventArgs《DateTime》(oldValue, newValue,TimeUpdatedEvent);
            this.RaiseEvent(arg);
            
        }

3,为控件添加命令(Commands)
能为自定义控件添加如WPF内置控件一样的命令是一件很不错的事情(事实上这也是在CustomControl中降低界面和后台逻辑耦合度的一种方法,本系列随笔中的下一篇中将会具体谈谈).
WPF中内置的命令有两大类型:RoutedCommand以及RoutedUICommand,后者比前者多了一个Text属性用于在界面上自动本地化地显示该命令对应的文本,更多的可以参考WPF中的命令与命令绑定(一)以及WPF中的命令与命令绑定(二).
这里我们来定义一个命令,其功能是控件的语音报时.首先我们定义一个命令:

public static readonly RoutedUICommand SpeakCommand = new RoutedUICommand(“Speak“, “Speak“, typeof(ClockUserCtrl));

参数分别为命名的显示名称,命令的名称,命令的拥有者类型.
然后在控件的静态函数中定义一个命令绑定,该命令绑定定义了命令的具体细节:对应的命令是什么?其完成什么样的功能,当前环境下其能执行吗?

     CommandBinding commandBinding = new CommandBinding(SpeakCommand, new ExecutedRoutedEventHandler(ExecuteSpeak),                
     new CanExecuteRoutedEventHandler(CanExecuteSpeak));
        private static void ExecuteSpeak(object sender, ExecutedRoutedEventArgs arg)        
        {            
            ClockUserCtrl clock = sender as ClockUserCtrl;            
            if (clock != null)            
            {                
            clock.SpeakTheTime();            
            }        
        }        
        private static void CanExecuteSpeak(object sender, CanExecuteRoutedEventArgs arg)        
        {            
        ClockUserCtrl clock = sender as ClockUserCtrl;            
        arg.CanExecute = (clock != null);        
        }

CanExecuteRoutedEventArgs的CanExecute属性用于指示当前命令是否可用,也就是说系统会不断地检视该命令与该命令的作用对象,并根据你所提供的条件来判断当前命令是否可用,比如文本框状态变为“只读“后,其“粘贴“命令将不可用,作用于该文本框的粘贴按钮会自动被禁用,反之则启用.
new ExecutedRoutedEventHandler(ExecuteSpeak)委托指定了当该命令被执行时所要完成的任务,这通过回调ExcuteSpeak函数来实现.

private static void ExecuteSpeak(object sender, ExecutedRoutedEventArgs arg)        
{            
                ClockUserCtrl clock = sender as ClockUserCtrl;      
                if (clock != null)            
                {                
                clock.SpeakTheTime();            
                }        
  }
 private void SpeakTheTime()        
  {            
        DateTime localTime = this.Time.ToLocalTime();
        string textToSpeak = “现在时刻,“ + localTime.ToShortDateString() +“,“+ localTime.ToShortTimeString() +“,星期“ + (int)localTime.DayOfWeek;            
        this.speecher.SpeakAsync(textToSpeak);        
   }

我们也可以为命令添加快捷键,这是通过InputBinding来实现的,其将命令与命令的快捷键关联起来,比如:

InputBinding inputBinding = new InputBinding(SpeakCommand, new MouseGesture(MouseAction.LeftClick));
CommandManager.RegisterClassInputBinding(typeof(ClockUserCtrl), inputBinding);

这样,当我们鼠标点击控件时就会引发控件的Speak命令,从而调用SpeakTheTime函数进行语音播报.
快捷键可以通过MouseGesture或KeyGesture来定义.