WPF 的Canvas畫圖區整體縮放與平移(二)
WPF物件都具有RenderTransform的屬性,可以通過設定RenderTransform來對WPF的元素進行變換,無論是控制元件還是形狀都可以變換。典型的變換包括縮小放大與平移。
(一)縮放
(二)平移
為了實現平移,這裡以按下滑鼠中間鍵並移動滑鼠作為事件觸發方式,來實現平移。即先下轄滑鼠中鍵(滾輪鍵),移動滑鼠,這樣WPF元素就會跟隨滑鼠平移。
WPF元素和形狀的變換是針對於自身的座標系的,因此不能直接獲取其自身座標系的點來進行變換,否則是不對了,有時也會不停閃爍跳動,本人一開始不小心用錯了,就用待平移的WPF元素自身座標點來作為平移引數,後來發現螢幕不停閃爍跳動,且平移距離也不正確。
如果採用Canvas作為畫板來繪製一些形狀,想要通過滑鼠或觸控操作來進行平移,那麼不能簡單地對canvas進行變換,否則Cancas平移的時候就會覆蓋周邊的其它控制元件,也就是Canvas畫布自身被移動了,而不僅僅是Canvas內部畫出來的形狀被移動了。
那如果需要實現移動怎麼辦呢?與前一篇文章一樣,我採取的方式是在Canvas外面包裝一個WPF元素,比如Border元素。這樣,Canvas就成為Border元素的子元素了,然後在Border元素上實現滑鼠控制操作來變換Canvas元素。
<Border Name="outside" Grid.Column="1" Background="LightBlue" PreviewMouseDown="outsidewrapper_PreviewMouseDown" PreviewMouseMove="outsidewrapper_PreviewMouseMove" PreviewMouseUp="outside_PreviewMouseUp" PreviewMouseWheel="outside_PreviewMouseWheel" ClipToBounds="True"> <Canvas Name="inside" Width="{Binding Path=ActualWidth,RelativeSource={RelativeSource AncestorType=Border}}" Height="{Binding Path=ActualHeight,RelativeSource={RelativeSource AncestorType=Border}}"> <Canvas.RenderTransform> <TransformGroup/> </Canvas.RenderTransform> <Line Canvas.Left="50" Canvas.Top="50" X1="100" Y1="200" X2="100" Y2="200" Stroke="Black" StrokeThickness="5"/> <Rectangle Canvas.Left="150" Canvas.Top="150" Width="380" Height="296" Fill="Red" /> </Canvas> </Border>
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } Point previousPoint; bool isTranslateStart = false; private void outsidewrapper_PreviewMouseDown(object sender, MouseButtonEventArgs e) { //說明:雖然WPF元素存在MouseLeftButtonDown和MouseRightButtonDown事件, //但卻沒找到MouseMiddleButtonDown事件。 //然而,幸運的是,MouseDown事件是不論哪個滑鼠鍵按下的時候都會觸發的,因此可以利用該事件, //並通過檢測哪個鍵被按下的狀態引數來判斷是否是中間鍵被按下了。 //也就是說,當出現MouseDown事件的時候,判斷三個鍵的狀態,如果中間鍵被按下, //而其它兩個鍵沒有被按下,則認為是中間鍵按下的事件。在該事件中,記錄下當時滑鼠的位置 if(e.MiddleButton==MouseButtonState.Pressed&&e.LeftButton==MouseButtonState.Released&&e.RightButton==MouseButtonState.Released) { previousPoint = e.GetPosition(outside); isTranslateStart = true; } e.Handled = true; } private void outsidewrapper_PreviewMouseMove(object sender, MouseEventArgs e) { if (e.MiddleButton == MouseButtonState.Pressed && e.LeftButton == MouseButtonState.Released && e.RightButton == MouseButtonState.Released) { if (isTranslateStart) { Point currentPoint = e.GetPosition(outside); //不能用 inside,必須用outside Vector v = currentPoint - previousPoint; TransformGroup tg = inside.RenderTransform as TransformGroup; tg.Children.Add(new TranslateTransform(v.X,v.Y)); //centerX和centerY用外部包裝元素的座標,不能用內部被變換的Canvas元素的座標 // inside.RenderTransform = tg; previousPoint = currentPoint; } } e.Handled = true; } private void outside_PreviewMouseUp(object sender, MouseButtonEventArgs e) { if (e.MiddleButton == MouseButtonState.Pressed && e.LeftButton == MouseButtonState.Released && e.RightButton == MouseButtonState.Released) { if (isTranslateStart) { isTranslateStart = false; } } e.Handled = true; } private void outside_PreviewMouseWheel(object sender, MouseWheelEventArgs e) { Point currentPoint = e.GetPosition(outside); //不能用 inside,必須用outside TransformGroup tg = inside.RenderTransform as TransformGroup; double s = ((double)e.Delta) / 1000.0+1.0; //centerX和centerY用外部包裝元素的座標,不能用內部被變換的Canvas元素的座標 tg.Children.Add(new ScaleTransform(s,s,currentPoint.X,currentPoint.Y)); e.Handled = true; } }
需要注意事項包括:
(1)包裝的元素需要新增ClipToBounds="True"屬性,這樣內部Canvas超出包裝元素的時候,超出部分就會被裁剪掉;
(2)把Canvas元素的初始大小設定為與包裝元素一樣大小,可以通過RelativeSource來設定:Width="{Binding Path=ActualWidth,RelativeSource={RelativeSource AncestorType=Border}}" ,Height="{Binding Path=ActualHeight,RelativeSource={RelativeSource AncestorType=Border}}"。
(3)將外包包裝元素的背景和Canvas的背景設定為一樣的背景,這樣當Canvas平移的時候,不會給然感覺Canvas畫布在移動,而是感覺內部元素(比如線、多邊形、圓形、矩形、文字等等)。或者Canvas不設定背景也是可行的(但可能Canvas會收不到滑鼠事件......)。
(4)用滑鼠事件控制平移的話,可以使用隧道事件,且事件應該關聯在外部包裝元素Border上。平移的大小用外包裝元素座標系統中的座標點進行計算。當然具體根據需要採用合適的事件,比如滾輪事件之類的,觸控事件之類的。
(5)雖然WPF元素存在MouseLeftButtonDown和MouseRightButtonDown事件,但卻沒找到MouseMiddleButtonDown事件。然而,幸運的是,MouseDown事件是不論哪個滑鼠鍵按下的時候都會觸發的,因此可以利用該事件,並通過檢測哪個鍵被按下的狀態引數來判斷是否是中間鍵被按下了。也就是說,當出現MouseDown事件的時候,判斷三個鍵的狀態,如果中間鍵被按下,而其它兩個鍵沒有被按下,則認為是中間鍵按下的事件。在該事件中,記錄下當時滑鼠的位置。滑鼠移動的時候,獲取新的滑鼠位置,並與記錄為物件成員變數的前一個滑鼠位置相減,即刻得知平移量,通過向Canvas的TransformGroup中新增平移變換,即可實現平移變換。
(6)理解:按理說所有元素的變換都是針對自身的座標體系的,而不是針對外部父元素的座標體系的,但是WPF有個特點,雖然時針對自身座標系的變化,但是變換前後自身的ActualWidth和ActualHeight都沒有發生任何變化,下次繼續變換的時候,還是用的ActualWidth和ActualHeigth作為自身座標系的參考用途,而不是按照變換後的實際尺寸在定位自身座標系尺寸的。所以,使用外部包裝元素的座標系來給定每次變換的(CenterX和CenterY)是可行的。這一點需要慢慢理解。