真・WPF 按钮拖动和调整大小

  • A+
所属分类:.NET技术
摘要

真・WPF 按钮拖动和调整大小独立观察员 2020 年 8 月 29 日手头有个 Winform 程序,是使用动态生成按钮,然后拖动、调整大小,以此来记录一些坐标数据,最后保存坐标数据的。

真・WPF 按钮拖动和调整大小

独立观察员 2020 年 8 月 29 日

手头有个 Winform 程序,是使用动态生成按钮,然后拖动、调整大小,以此来记录一些坐标数据,最后保存坐标数据的。

在数据量(按钮数量)比较小的时候是使用得挺愉快的,但是,当按钮数上去之后,比如达到四五百个按钮,那就比较痛苦了。具体来说就是,无论是移动窗口,还是拖动滚动条,或者是最小化窗口再还原,都会引起界面重绘,表现为按钮一个接一个地出现。

经过实测,与电脑的性能和 GPU 都没有关系,网上针对 Winform 这个问题的解决方案,比如开启双缓冲等,都大致尝试了,并无任何起色,反而可能更糟。所以就像网友所说,这个要么不要在同一个界面上放置太多控件;要么使用 WPF,毕竟 WPF 采用的是 DirectX 作为底层绘图引擎,而 Winform 则采用传统的 GDI 。由于业务需求,不让在界面上放置过多控件的方案不太可行,或者说暂未想到有什么变通的办法,所以决定改版为 WPF 试试。

 

经过几天的改造,原 Winform 版软件的一小部分功能已改版为 WPF 版,而且成果喜人,同样的按钮数量,现在无论怎样折腾,这几百个按钮就如同钉在了界面上一样,不再能看到他们载入的过程了。在这个改造的过程中,我是将 Winform 版软件中关于按钮拖动和调整大小的代码改造为 WPF 版的,听上去挺简单的,但是还是碰到了一些问题,比如 WPF 屏蔽了鼠标左键的一些事件,需要额外处理一下,还有的就是关于坐标定位的一些问题了,下面将给出一些关键代码,和大家相互交流学习一下。

 

首先,先上一道小菜,解决一下 WPF 按钮控件(Button)中文字自动换行的问题。

不对,还是先看看 Demo 的界面结构吧:

真・WPF 按钮拖动和调整大小

 

其它控件和布局就不说了(最后会给出 Demo 地址),关键的是中间这个 ScrollViewer 包裹的 Canvas,我们生成的按钮都是在这个 Canvas 上的,拖动和调整大小也是。Winform 的布局是依赖于坐标的,WPF 的布局控件则基本是不使用坐标定位的,甚至都不推荐指定大小,而只有 Canvas 布局控件保留了以坐标定位的模式,正好适合我们的需求(之前 Winform 版使用的是 Panel 控件)。

可以看到里面我还注释了一个 Button ,这个就是用来演示我们的 “小菜” 问题(按钮文字自动换行)的。我们先把注释放开,并且只保留其宽和高的设置:

真・WPF 按钮拖动和调整大小

 

可以看到当按钮宽度窄于文本内容时,文本内容并不能进行自动换行,且 Button 控件并没有相关属性进行设置。解决方法就是在按钮中添加 TextBlock 控件,然后设置其 TextWrapping 属性,当然,这里我们不直接这样写,而是使用内容模板:

<Button Width="38" Height="75" ContentTemplate="{DynamicResource DataTemplateButtonWrap}">1A005</Button>

 

这个模板的资源放在 App.xaml 中:

<Application.Resources>     <DataTemplate x:Key="DataTemplateButtonWrap" DataType="Button">         <Grid>             <TextBlock TextWrapping="Wrap" Text="{TemplateBinding Content}"></TextBlock>         </Grid>     </DataTemplate> </Application.Resources>

 

TextBlock 中使用了 TemplateBinding 将 Button 的 Content “绑架” 到了自己的 Text 中,哈哈。看看效果:

真・WPF 按钮拖动和调整大小

 

至于后台动态绑定资源则是使用 SetResourceReference 方法,后面代码里也有体现。

 

好了,小菜吃完了,开始吃主菜吧:

#region 成员  private Control _control; private int _btnNum = 0;  #endregion  /// <summary> /// 设置控件在Canvas容器中的位置; /// </summary> private void SetControlLocation(Control control, Point point) {     Canvas.SetLeft(control, point.X);     Canvas.SetTop(control, point.Y); }  /// <summary> /// 添加按钮 /// </summary> private void AddBtnHandler() {     string btnContent = GetBtnContent();      Button btn = new Button     {         Name = "btn" + btnContent,         Content = "btn" + btnContent,         Width = 80,         Height = 20,     };      _control = btn;     AddContorlToCanvas(_control);     SetControlLocation(_control, new Point(163, 55)); }  /// <summary> /// 添加控件到界面; /// </summary> /// <param name="control"></param> private void AddContorlToCanvas(Control control) {     control.MouseDown += MyMouseDown;     control.MouseLeave += MyMouseLeave;     //_control.MouseMove += MyMouseMove;     control.KeyDown += MyKeyDown;      //解决鼠标左键无法触发 MouseDown 的问题;     control.AddHandler(Button.MouseLeftButtonDownEvent, new MouseButtonEventHandler(MyMouseDown), true);     control.AddHandler(Button.MouseMoveEvent, new MouseEventHandler(MyMouseMove), true);      CanvasMain.Children.Add(control);      if (control is Button)     {         //模板中设置按钮文字换行(模板资源在App.xaml中);         control.SetResourceReference(ContentTemplateProperty, "DataTemplateButtonWrap");         _btnNum++;     } }  /// <summary> /// 生成按钮内容 /// </summary> /// <returns></returns> private string GetBtnContent() {     return (_btnNum + 1).ToString().PadLeft(3, '0'); }  /// <summary> /// 删除按钮 /// </summary> private void DelBtnHandler() {     CanvasMain.Children.Remove(_control); }

 

上面代码是对按钮生成、添加到界面的一些操作逻辑,每个方法都有注释,具体的大家自己看看,这里就不在赘述了。其中 添加控件到界面 的方法 AddContorlToCanvas 中,给控件(本文指的是按钮)添加了 MouseDown、MouseLeave、MouseMove、KeyDown 等鼠标键盘事件,然后开头说过,WPF 屏蔽了 Button 的鼠标左键的一些事件,所以需要使用 AddHandler 进行处理。

 

下面来看看主菜中的精华:

#region 实现窗体内的控件拖动  const int Band = 5; const int BtnMinWidth = 10; const int BtnMinHeight = 10; private EnumMousePointPosition _enumMousePointPosition; private Point _point; //记录鼠标上次位置;  #region btn按钮拖动  /// <summary> /// 鼠标按下 /// </summary> private void MyMouseDown(object sender, MouseEventArgs e) {     //选择当前的按钮     Button button = (Button)sender;     _control = button;     //Point point = e.GetPosition(CanvasMain);      //左键点击按钮后可按WSAD进行上下左右移动;     if (e.LeftButton == MouseButtonState.Pressed)     {         button.KeyDown += new KeyEventHandler(MyKeyDown);     }      double left = Canvas.GetLeft(_control);     double top = Canvas.GetTop(_control);      //右键点击按钮可向选定方向生成新按钮;     if (e.RightButton == MouseButtonState.Pressed)     {         Button btn = new Button         {             Name = "btn" + GetBtnContent(),              Content = GetStrEndNumAddOne(button.Content.ToString())         };          CheckRepeat(btn.Content.ToString());          btn.Width = _control.Width;         btn.Height = _control.Height;          if (rbUpper.IsChecked == true)//         {             int h = txtUpper.Text.Trim() == "" ? 0 : Convert.ToInt32(txtUpper.Text.Trim());             SetControlLocation(btn, new Point(left, top - _control.Height - h));         }         if (rbLower.IsChecked == true)//         {             int h = txtLower.Text.Trim() == "" ? 0 : Convert.ToInt32(txtLower.Text.Trim());             SetControlLocation(btn, new Point(left, top + _control.Height + h));         }         if (rbLeft.IsChecked == true)//         {             int w = txtLeft.Text.Trim() == "" ? 0 : Convert.ToInt32(txtLeft.Text.Trim());             SetControlLocation(btn, new Point(left - _control.Width - w, top));         }         if (rbRight.IsChecked == true)//         {             int w = txtRight.Text.Trim() == "" ? 0 : Convert.ToInt32(txtRight.Text.Trim());             SetControlLocation(btn, new Point(left + _control.Width + w, top));         }          _control = btn;         AddContorlToCanvas(_control);     }          //TODO 中键点击按钮可进行信息编辑; }  /// <summary> /// 检查重复内容按钮 /// </summary> /// <param name="content"></param> private void CheckRepeat(string content) {     foreach (Control c in CanvasMain.Children)     {         if (c is Button btn)         {             if (content == btn.Content.ToString())             {                 MessageBox.Show("出现重复按钮内容:" + content, "提示");                 return;             }         }     } }  /// <summary> /// 获取非纯数字字符串的数值加一结果; /// </summary> private string GetStrEndNumAddOne(string str) {     int numberIndex = 0; //数字部分的起始位置;     int charIndex = 0;     foreach (char tempchar in str.ToCharArray())     {         charIndex++;         if (!char.IsNumber(tempchar))         {             numberIndex = charIndex;         }     }      string prefix = str.Substring(0, numberIndex);     string numberStrOrigin = str.Remove(0, numberIndex);     string numberStrTemp = "";      if (numberStrOrigin != "")     {         numberStrTemp = (Convert.ToInt32(numberStrOrigin) + 1).ToString();     }      string result = "";     if (numberStrOrigin.Length <= numberStrTemp.Length)     {         result = prefix + numberStrTemp;     }     else     {         result = prefix + numberStrTemp.PadLeft(numberStrOrigin.Length, '0');     }      return result; }  /// <summary> /// 鼠标离开 /// </summary> private void MyMouseLeave(object sender, EventArgs e) {     _enumMousePointPosition = EnumMousePointPosition.MouseSizeNone;     _control.Cursor = Cursors.Arrow; }  /// <summary> /// 鼠标移动 /// </summary> private void MyMouseMove(object sender, MouseEventArgs e) {     _control = (Control)sender;     double left = Canvas.GetLeft(_control);     double top = Canvas.GetTop(_control);     Point point = e.GetPosition(CanvasMain);     double height = _control.Height;     double width = _control.Width;      if (e.LeftButton == MouseButtonState.Pressed)     {         switch (_enumMousePointPosition)         {             case EnumMousePointPosition.MouseDrag:                  SetControlLocation(_control, new Point(left + point.X - _point.X, top + point.Y - _point.Y));                  break;              case EnumMousePointPosition.MouseSizeBottom:                  height += point.Y - _point.Y;                  break;              case EnumMousePointPosition.MouseSizeBottomRight:                  width += point.X - _point.X;                 height += point.Y - _point.Y;                  break;              case EnumMousePointPosition.MouseSizeRight:                  width += point.X - _point.X;                  break;              case EnumMousePointPosition.MouseSizeTop:                  SetControlLocation(_control, new Point(left, top + point.Y - _point.Y));                 height -= (point.Y - _point.Y);                  break;              case EnumMousePointPosition.MouseSizeLeft:                  SetControlLocation(_control, new Point(left + point.X - _point.X, top));                 width -= (point.X - _point.X);                  break;              case EnumMousePointPosition.MouseSizeBottomLeft:                  SetControlLocation(_control, new Point(left + point.X - _point.X, top));                 width -= (point.X - _point.X);                 height += point.Y - _point.Y;                  break;              case EnumMousePointPosition.MouseSizeTopRight:                  SetControlLocation(_control, new Point(left, top + point.Y - _point.Y));                 width += (point.X - _point.X);                 height -= (point.Y - _point.Y);                  break;              case EnumMousePointPosition.MouseSizeTopLeft:                  SetControlLocation(_control, new Point(left + point.X - _point.X, top + point.Y - _point.Y));                 width -= (point.X - _point.X);                 height -= (point.Y - _point.Y);                  break;              default:                  break;         }          //记录光标拖动到的当前点         _point.X = point.X;         _point.Y = point.Y;          if (width < BtnMinWidth) width = BtnMinWidth;         if (height < BtnMinHeight) height = BtnMinHeight;         _control.Width = width;         _control.Height = height;     }     else     {         _enumMousePointPosition = GetMousePointPosition(_control, e); //'判断光标的位置状态          switch (_enumMousePointPosition) //'改变光标         {             case EnumMousePointPosition.MouseSizeNone:                 _control.Cursor = Cursors.Arrow;       //'箭头                 break;              case EnumMousePointPosition.MouseDrag:                 _control.Cursor = Cursors.SizeAll;     //'四方向                 break;              case EnumMousePointPosition.MouseSizeBottom:                 _control.Cursor = Cursors.SizeNS;      //'南北                 break;              case EnumMousePointPosition.MouseSizeTop:                 _control.Cursor = Cursors.SizeNS;      //'南北                 break;              case EnumMousePointPosition.MouseSizeLeft:                 _control.Cursor = Cursors.SizeWE;      //'东西                 break;              case EnumMousePointPosition.MouseSizeRight:                 _control.Cursor = Cursors.SizeWE;      //'东西                 break;              case EnumMousePointPosition.MouseSizeBottomLeft:                 _control.Cursor = Cursors.SizeNESW;    //'东北到南西                 break;              case EnumMousePointPosition.MouseSizeBottomRight:                 _control.Cursor = Cursors.SizeNWSE;    //'东南到西北                 break;              case EnumMousePointPosition.MouseSizeTopLeft:                 _control.Cursor = Cursors.SizeNWSE;    //'东南到西北                 break;              case EnumMousePointPosition.MouseSizeTopRight:                 _control.Cursor = Cursors.SizeNESW;    //'东北到南西                 break;              default:                 break;         }     } }  /// <summary> /// 按键WSAD(上下左右) /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void MyKeyDown(object sender, KeyEventArgs e) {     double left = Canvas.GetLeft(_control);     double top = Canvas.GetTop(_control);      switch (e.Key)     {         case Key.W://         {             SetControlLocation(_control, new Point(left, top-1));             break;         }         case Key.S://         {             SetControlLocation(_control, new Point(left, top+1));             break;         }         case Key.A://         {             SetControlLocation(_control, new Point(left-1, top));             break;         }         case Key.D://         {             SetControlLocation(_control, new Point(left+1, top));             break;         }     } }  #endregion 按钮拖动  #region 鼠标位置  /// <summary> /// 鼠标指针位置枚举; /// </summary> private enum EnumMousePointPosition {     /// <summary>     ////// </summary>     MouseSizeNone = 0,      /// <summary>     /// 拉伸右边框     /// </summary>     MouseSizeRight = 1,      /// <summary>     /// 拉伸左边框     /// </summary>     MouseSizeLeft = 2,      /// <summary>     /// 拉伸下边框     /// </summary>     MouseSizeBottom = 3,      /// <summary>     /// 拉伸上边框     /// </summary>     MouseSizeTop = 4,      /// <summary>     /// 拉伸左上角     /// </summary>     MouseSizeTopLeft = 5,      /// <summary>     /// 拉伸右上角     /// </summary>     MouseSizeTopRight = 6,      /// <summary>     /// 拉伸左下角     /// </summary>     MouseSizeBottomLeft = 7,      /// <summary>     /// 拉伸右下角     /// </summary>     MouseSizeBottomRight = 8,             /// <summary>     /// 鼠标拖动     /// </summary>     MouseDrag = 9 }  /// <summary> /// 获取鼠标指针位置; /// </summary> /// <param name="control"></param> /// <param name="e"></param> /// <returns></returns> private EnumMousePointPosition GetMousePointPosition(Control control, MouseEventArgs e) {     Size size = control.RenderSize;     Point point = e.GetPosition(control);      Point pointCanvas = e.GetPosition(CanvasMain);     _point.X = pointCanvas.X;     _point.Y = pointCanvas.Y;      if ((point.X >= -1 * Band) | (point.X <= size.Width) | (point.Y >= -1 * Band) | (point.Y <= size.Height))     {         if (point.X < Band)         {             if (point.Y < Band)             {                 return EnumMousePointPosition.MouseSizeTopLeft;             }             else             {                 if (point.Y > -1 * Band + size.Height)                 {                     return EnumMousePointPosition.MouseSizeBottomLeft;                 }                 else                 {                     return EnumMousePointPosition.MouseSizeLeft;                 }             }         }         else         {             if (point.X > -1 * Band + size.Width)             {                 if (point.Y < Band)                 {                     return EnumMousePointPosition.MouseSizeTopRight;                 }                 else                 {                     if (point.Y > -1 * Band + size.Height)                     {                         return EnumMousePointPosition.MouseSizeBottomRight;                     }                     else                     {                         return EnumMousePointPosition.MouseSizeRight;                     }                 }             }             else             {                 if (point.Y < Band)                 {                     return EnumMousePointPosition.MouseSizeTop;                 }                 else                 {                     if (point.Y > -1 * Band + size.Height)                     {                         return EnumMousePointPosition.MouseSizeBottom;                     }                     else                     {                         return EnumMousePointPosition.MouseDrag;                     }                 }             }         }     }     else     {         return EnumMousePointPosition.MouseSizeNone;     } }  #endregion 鼠标位置  #endregion 实现窗体内的控件拖动

 

俗话说,Talk is cheap,show me the code。那么既然代码已给出,大家就直接批评指正呗,我也没什么说的了(主要是肚子饿了)。

给个效果图吧:

真・WPF 按钮拖动和调整大小

 

动图:

真・WPF 按钮拖动和调整大小

 

最后给出 Demo 地址:

https://gitee.com/dlgcy/Practice/tree/master/WPFPractice 

 

同步首发:

http://dlgcy.com/real-wpf-button-drag-and-resize/

微信公众号