真・WPF 按钮拖动和调整大小
真?WPF?按鈕拖動和調(diào)整大小
獨立觀察員 2020 年 8 月 29 日
手頭有個 Winform 程序,是使用動態(tài)生成按鈕,然后拖動、調(diào)整大小,以此來記錄一些坐標數(shù)據(jù),最后保存坐標數(shù)據(jù)的。
在數(shù)據(jù)量(按鈕數(shù)量)比較小的時候是使用得挺愉快的,但是,當按鈕數(shù)上去之后,比如達到四五百個按鈕,那就比較痛苦了。具體來說就是,無論是移動窗口,還是拖動滾動條,或者是最小化窗口再還原,都會引起界面重繪,表現(xiàn)為按鈕一個接一個地出現(xiàn)。
經(jīng)過實測,與電腦的性能和 GPU 都沒有關系,網(wǎng)上針對 Winform 這個問題的解決方案,比如開啟雙緩沖等,都大致嘗試了,并無任何起色,反而可能更糟。所以就像網(wǎng)友所說,這個要么不要在同一個界面上放置太多控件;要么使用 WPF,畢竟 WPF 采用的是 DirectX 作為底層繪圖引擎,而 Winform 則采用傳統(tǒng)的 GDI 。由于業(yè)務需求,不讓在界面上放置過多控件的方案不太可行,或者說暫未想到有什么變通的辦法,所以決定改版為 WPF 試試。
?
經(jīng)過幾天的改造,原 Winform 版軟件的一小部分功能已改版為 WPF 版,而且成果喜人,同樣的按鈕數(shù)量,現(xiàn)在無論怎樣折騰,這幾百個按鈕就如同釘在了界面上一樣,不再能看到他們載入的過程了。在這個改造的過程中,我是將 Winform 版軟件中關于按鈕拖動和調(diào)整大小的代碼改造為 WPF 版的,聽上去挺簡單的,但是還是碰到了一些問題,比如 WPF 屏蔽了鼠標左鍵的一些事件,需要額外處理一下,還有的就是關于坐標定位的一些問題了,下面將給出一些關鍵代碼,和大家相互交流學習一下。
?
首先,先上一道小菜,解決一下 WPF 按鈕控件(Button)中文字自動換行的問題。
不對,還是先看看 Demo 的界面結(jié)構(gòu)吧:
?
其它控件和布局就不說了(最后會給出 Demo 地址),關鍵的是中間這個 ScrollViewer 包裹的 Canvas,我們生成的按鈕都是在這個 Canvas 上的,拖動和調(diào)整大小也是。Winform 的布局是依賴于坐標的,WPF 的布局控件則基本是不使用坐標定位的,甚至都不推薦指定大小,而只有 Canvas 布局控件保留了以坐標定位的模式,正好適合我們的需求(之前 Winform 版使用的是 Panel 控件)。
可以看到里面我還注釋了一個?Button?,這個就是用來演示我們的 “小菜” 問題(按鈕文字自動換行)的。我們先把注釋放開,并且只保留其寬和高的設置:
?
可以看到當按鈕寬度窄于文本內(nèi)容時,文本內(nèi)容并不能進行自動換行,且 Button 控件并沒有相關屬性進行設置。解決方法就是在按鈕中添加 TextBlock 控件,然后設置其 TextWrapping 屬性,當然,這里我們不直接這樣寫,而是使用內(nèi)容模板:
<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 中,哈哈。看看效果:
?
至于后臺動態(tài)綁定資源則是使用 SetResourceReference 方法,后面代碼里也有體現(xiàn)。
?
好了,小菜吃完了,開始吃主菜吧:
#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;// 解決鼠標左鍵無法觸發(fā) 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> /// 生成按鈕內(nèi)容 /// </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 實現(xiàn)窗體內(nèi)的控件拖動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> /// 檢查重復內(nèi)容按鈕 /// </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("出現(xiàn)重復按鈕內(nèi)容:" + content, "提示");return;}}} }/// <summary> /// 獲取非純數(shù)字字符串的數(shù)值加一結(jié)果; /// </summary> private string GetStrEndNumAddOne(string str) {int numberIndex = 0; // 數(shù)字部分的起始位置;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); //' 判斷光標的位置狀態(tài)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 實現(xiàn)窗體內(nèi)的控件拖動?
俗話說,Talk is cheap,show me the code。那么既然代碼已給出,大家就直接批評指正唄,我也沒什么說的了(主要是肚子餓了)。
給個效果圖吧:
?
動圖:
?
最后給出 Demo 地址:
https://gitee.com/dlgcy/Practice/tree/master/WPFPractice?
?
總結(jié)
以上是生活随笔為你收集整理的真・WPF 按钮拖动和调整大小的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 做权限认证,还不了解IdentitySe
- 下一篇: .Net5发布在即,当心技术断层!