一路下来,ANode的大体结构基本要完工了,我之所以说这是个结构,因为,这是一次
渐进式开发,各个部分的处理方式,大体上已经确定下来了,如有后续内容,将依照现在
的模式去做。
今天说一下代码中触发动画,这个话题其实很短,那就加上,ANode的一个简单测试环境
其实也是,后面我们UI主窗口的一个基本结构。
从代码中触发动画其实只有一句话,简单到不行。
BeginStoryboard(Me.Resources.Item("SelectedStatus"))
SelectedStatus就是我们当初在Xaml中定义的画板,用BeginStoryboard执行一下,就
Ok。
ANode的事件触发,目前的几种状态触发,我都是通过代码实现的,因为,在触发的同时
要抛出事件,还有设置状态标识,为了统一起见,我都放在代码中实现了。
还是列表的形式来说明各个状态是在什么情况下出现的吧
ANode的状态触发条件 | | | |
状态 | 触发条件 | 附加条件 | 响应 |
选中状态 | 鼠标进入ANode | 非编辑状态 | 1.触发动画 2.设置当前ANode为活动 (关系到失去焦点的ANode,恢复正常状态) 3.抛出OnSelected事件 |
| 鼠标在NodeGrid上抬起 | 无 | 1.触发动画 2.抛出DragEnd事件 |
拖放状态 | 鼠标在NodeGrid上按下 | 无 | 1.触发动画 2.抛出DragStart事件 |
编辑状态 | 鼠标在NodeTextInput上抬起 | 无 | 1.触发动画 2.设置IsEdit标志位 |
正常状态 | 自定义方法SetNormal | 无 | 1.触发动画 2.设置IsEdit=False |
在这里要先说明一下,ANode在主界面上,会有很多很多,但是,每次选中的只有一个,
不会存在多选的形式(至少目前,我所预期的方式不会),那么当ANode被选中时,将取消
上一个被选中的ANode,在这里我用了一个Shared方法,来实现同一的管理。
1 Private Shared ActiveNode As ANode 2 3 Private Shared Sub SetActiveNode( ByVal n As ANode) 4 If Not ActiveNode Is Nothing Then 5 If Not ActiveNode.Equals(n) Then 6 ActiveNode.ZIndex = 0 7 ActiveNode.SetNormal() 8 End If 9 End If 10 ActiveNode = n 11 If Not n Is Nothing Then 12 n.ZIndex = 1 13 End If 14 End Sub 15 16 Public Shared Sub SetAllNormal() 17 SetActiveNode( Nothing ) 18 End Sub 一个静态的变量标识这整个ANode群的活动单元,一个私有的SetActiveNode,
用来当当前单元被选中时将自己设置为活动单元,并取消前一个单元的活动状态。
并且,加了一个SetAllNormal方法,给外部提供的全部取消活动的法。
以下是各个状态相关的方法和变量的代码
1 2 #Region " 选择状态 " 3 Public Event OnSelected( ByVal sender As ANode) 4 5 Private Sub NodeGrid_MouseEnter( ByVal sender As Object , ByVal e As System.Windows.Input.MouseEventArgs) Handles NodeGrid.MouseEnter 6 If IsEdit Then Exit Sub 7 SetActiveNode( Me ) 8 BeginStoryboard( Me .Resources.Item( " SelectedStatus " )) 9 RaiseEvent OnSelected( Me ) 10 End Sub 11 #End Region 12 13 #Region " 拖放状态 " 14 Public Event OnDragStart( ByVal sender As ANode, ByVal DragPoint As Point) 15 Public Event OnDragEnd( ByVal sender As ANode) 16 17 Private Sub NodeGrid_MouseDown( ByVal sender As Object , ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles NodeGrid.MouseDown 18 BeginStoryboard( Me .Resources.Item( " DragStatus " )) 19 RaiseEvent OnDragStart( Me , e.GetPosition(sender)) 20 e.Handled = True 21 End Sub 22 23 Private Sub NodeGrid_MouseUp( ByVal sender As Object , ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles NodeGrid.MouseUp 24 BeginStoryboard( Me .Resources.Item( " SelectedStatus " )) 25 RaiseEvent OnDragEnd( Me ) 26 End Sub 27 #End Region 28 29 #Region " 编辑状态 " 30 Private IsEdit As Boolean = False 31 Private Sub NodeTextInput_PreviewMouseDown( ByVal sender As Object , ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles NodeTextInput.PreviewMouseDown 32 BeginStoryboard( Me .Resources.Item( " EditStatus " )) 33 IsEdit = True 34 End Sub 35 #End Region 36 37 #Region " 普通状态 " 38 Public Sub SetNormal() 39 BeginStoryboard( Me .Resources.Item( " NormalStatus " )) 40 IsEdit = False 41 End Sub 42 #End Region 43 44 #Region " ActiveNode " 45 Private Shared ActiveNode As ANode 46 47 Private Shared Sub SetActiveNode( ByVal n As ANode) 48 If Not ActiveNode Is Nothing Then 49 If Not ActiveNode.Equals(n) Then 50 ActiveNode.ZIndex = 0 51 ActiveNode.SetNormal() 52 End If 53 End If 54 ActiveNode = n 55 If Not n Is Nothing Then 56 n.ZIndex = 1 57 End If 58 End Sub 59 60 Public Shared Sub SetAllNormal() 61 SetActiveNode( Nothing ) 62 End Sub 63 #End Region 至此,ANode貌似就差不多介绍完了
哦,还有几个属性,Text,Background,Foreground,ZIndex
为了可以支持绑定,这些属性都是DependencyProperty。
代码也贴一下。
1 2 #Region " Text DependencyProperty " 3 ''' <summary> 4 ''' PropertyComment 5 ''' </summary> 6 ''' <remarks></remarks> 7 Public Shared ReadOnly TextProperty As DependencyProperty = _ 8 DependencyProperty.Register( 9 " Text " , GetType ( String ), GetType (ANode), New PropertyMetadata( _ 10 "" , New PropertyChangedCallback( AddressOf TextPropertyChanged_CallBack))) 11 12 Public Property Text() As String 13 Get 14 Return GetValue(TextProperty) 15 End Get 16 Set ( ByVal Value As String ) 17 SetValue(TextProperty, Value) 18 End Set 19 End Property 20 21 Public Shared Sub TextPropertyChanged_CallBack( ByVal dp As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs) 22 23 End Sub 24 #End Region 25 26 #Region " BackgroundColor DependencyProperty " 27 ''' <summary> 28 ''' PropertyComment 29 ''' </summary> 30 ''' <remarks></remarks> 31 Public Shared ReadOnly BackgroundColorProperty As DependencyProperty = _ 32 DependencyProperty.Register( 33 " BackgroundColor " , GetType (Brush), GetType (ANode), New PropertyMetadata( _ 34 Brushes.LightBlue, New PropertyChangedCallback( AddressOf BackgroundColorPropertyChanged_CallBack))) 35 36 Public Property BackgroundColor() As Brush 37 Get 38 Return GetValue(BackgroundColorProperty) 39 End Get 40 Set ( ByVal Value As Brush) 41 SetValue(BackgroundColorProperty, Value) 42 End Set 43 End Property 44 45 Public Shared Sub BackgroundColorPropertyChanged_CallBack( ByVal dp As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs) 46 Dim an As ANode = CType (dp, ANode) 47 an.NodeBackground.Background = e.NewValue 48 an.NodeBackground.BorderBrush = e.NewValue 49 With CType (e.NewValue, SolidColorBrush).Color 50 Dim brightness As Integer = Math.Max(.R, Math.Max(.G, .B)) 51 If brightness < 127 Then 52 an.ForegroundColor = Brushes.White 53 Else 54 an.ForegroundColor = Brushes.Black 55 End If 56 End With 57 End Sub 58 #End Region 59 60 #Region " ForegroundColor DependencyProperty " 61 ''' <summary> 62 ''' PropertyComment 63 ''' </summary> 64 ''' <remarks></remarks> 65 Public Shared ReadOnly ForegroundColorProperty As DependencyProperty = _ 66 DependencyProperty.Register( 67 " ForegroundColor " , GetType (Brush), GetType (ANode), New PropertyMetadata( _ 68 Brushes.Black, New PropertyChangedCallback( AddressOf ForegroundColorPropertyChanged_CallBack))) 69 70 Public Property ForegroundColor() As Brush 71 Get 72 Return GetValue(ForegroundColorProperty) 73 End Get 74 Set ( ByVal Value As Brush) 75 SetValue(ForegroundColorProperty, Value) 76 End Set 77 End Property 78 79 Public Shared Sub ForegroundColorPropertyChanged_CallBack( ByVal dp As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs) 80 Dim an As ANode = CType (dp, ANode) 81 an.NodeText.Foreground = e.NewValue 82 End Sub 83 #End Region 84 85 #Region " ZIndex DependencyProperty " 86 ''' <summary> 87 ''' PropertyComment 88 ''' </summary> 89 ''' <remarks></remarks> 90 Public Shared ReadOnly ZIndexProperty As DependencyProperty = _ 91 DependencyProperty.Register( 92 " ZIndex " , GetType ( Double ), GetType (ANode), New PropertyMetadata( _ 93 0.0 , New PropertyChangedCallback( AddressOf ZIndexPropertyChanged_CallBack))) 94 95 Public Property ZIndex() As Double 96 Get 97 Return GetValue(ZIndexProperty) 98 End Get 99 Set ( ByVal Value As Double ) 100 SetValue(ZIndexProperty, Value) 101 End Set 102 End Property 103 104 Public Shared Sub ZIndexPropertyChanged_CallBack( ByVal dp As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs) 105 106 End Sub 107 #End Region Ok,ANode就完事儿了。
那么就搞一个对ANode的测试页面吧。
建立一个Wpf应用程序工程
MainWindowViewModel
1 Imports System.Collections.ObjectModel 2 3 Public Class MainWindowViewModel 4 Inherits NotificationObject 5 ' 6 ' NodeList As ObservableCollection(Of Node) 7 ' 8 Private mNodeList As ObservableCollection( Of Node) 9 Public Property NodeList() As ObservableCollection( Of Node) 10 Get 11 If mNodeList Is Nothing Then 12 mNodeList = New ObservableCollection( Of Node) 13 End If 14 Return mNodeList 15 End Get 16 Set ( ByVal Value As ObservableCollection( Of Node)) 17 mNodeList = Value 18 RaisePropertyChanged( " NodeList " ) 19 End Set 20 End Property 21 22 Public Sub New () 23 Dim newNode As New Node 24 newNode.Text = " 新主题 " 25 newNode.Top = 100 26 newNode.Left = 50 27 newNode.ZIndex = 0 28 newNode.Background = Brushes.LightCoral 29 NodeList.Add(newNode) 30 Dim node2 As New Node 31 node2.Text = " 副本 " 32 node2.Left = 200 33 node2.Top = 100 34 node2.ZIndex = 0 35 node2.Background = Brushes.Black 36 NodeList.Add(node2) 37 Dim node3 As New Node 38 With node3 39 .Text = " 副本 " 40 .Left = 300 41 .Top = 100 42 .ZIndex = 0 43 .Background = Brushes.Black 44 End With 45 46 NodeList.Add(node3) 47 End Sub 48 49 End Class 50 有一个用于绑定的NodeList属性。
为了简便起见,我自定义了NotificationObject类,而并没有引入Prism工具,
在ViewModel文件夹中,以后还是再建一个文件夹存放吧,现在稍显凌乱,也无伤大雅
关于NotificationObject,给不了解的同学补充一点东西吧,看代码。
1 Public Class NotificationObject 2 Implements ComponentModel.INotifyPropertyChanged 3 4 Public Event PropertyChanged( ByVal sender As Object , ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged 5 6 Public Sub RaisePropertyChanged( ByVal propertyName As String ) 7 RaiseEvent PropertyChanged( Me , New System.ComponentModel.PropertyChangedEventArgs(propertyName)) 8 End Sub 9 End Class 10 MVVM中,为了让绑定的属性跟控件属性建立真正的联系也就是说,当被绑定属性(ViewModel
或Model中的属性)变化时,在控件上有所体现,Model或ViewModel必须实现
INotifyPropertyChanged接口,以便于通知控件属性变化,在ViewModel或Model中的属性
要这样写
1 2 Private mText As String 3 Public Property Text() As String 4 Get 5 Return mText 6 End Get 7 Set ( ByVal Value As String ) 8 mText = Value 9 RaisePropertyChanged( " Text " ) 10 End Set 11 End Property 12 第9行,就是用来通知控件的。当然这里也要强调,控件属性必须是DependencyProperty才
能够被绑定。
Node类 对应与ANode,实现了几个需要被存储,和用于操作ANode在界面上的效果的几个属性。
这里,还有待于探讨,因为为了简便实现,Node参与了UI层的东西,而配合操作ANodeUI元素的
东西应该出现在ViewModel层。
1 2 Public Class Node 3 Inherits NotificationObject 4 5 ' 6 ' Text As String 7 ' 8 Private mText As String 9 Public Property Text() As String 10 Get 11 Return mText 12 End Get 13 Set ( ByVal Value As String ) 14 mText = Value 15 RaisePropertyChanged( " Text " ) 16 End Set 17 End Property 18 19 ' 20 ' Left As Double 21 ' 22 Private mLeft As Double 23 Public Property Left () As Double 24 Get 25 Return mLeft 26 End Get 27 Set ( ByVal Value As Double ) 28 mLeft = Value 29 RaisePropertyChanged( " Left " ) 30 End Set 31 End Property 32 33 ' 34 ' Top As Double 35 ' 36 Private mTop As Double 37 Public Property Top() As Double 38 Get 39 Return mTop 40 End Get 41 Set ( ByVal Value As Double ) 42 mTop = Value 43 RaisePropertyChanged( " Top " ) 44 End Set 45 End Property 46 47 ' 48 ' ZIndex As integer 49 ' 50 Private mZIndex As Integer = 0 51 Public Property ZIndex() As Integer 52 Get 53 Return mZIndex 54 End Get 55 Set ( ByVal Value As Integer ) 56 mZIndex = Value 57 RaisePropertyChanged( " ZIndex " ) 58 End Set 59 End Property 60 61 ' 62 ' Background As Brush 63 ' 64 Private mBackground As Brush = Brushes.LightBlue 65 Public Property Background() As Brush 66 Get 67 Return mBackground 68 End Get 69 Set ( ByVal Value As Brush) 70 mBackground = Value 71 RaisePropertyChanged( " Background " ) 72 End Set 73 End Property 74 75 End Class
跨界的属性也只是ZIndex而已,如果实在没办法,就这样吧,唉唉。
MainWindowViewModel
1 Imports System.Collections.ObjectModel 2 3 Public Class MainWindowViewModel 4 Inherits NotificationObject 5 ' 6 ' NodeList As ObservableCollection(Of Node) 7 ' 8 Private mNodeList As ObservableCollection( Of Node) 9 Public Property NodeList() As ObservableCollection( Of Node) 10 Get 11 If mNodeList Is Nothing Then 12 mNodeList = New ObservableCollection( Of Node) 13 End If 14 Return mNodeList 15 End Get 16 Set ( ByVal Value As ObservableCollection( Of Node)) 17 mNodeList = Value 18 RaisePropertyChanged( " NodeList " ) 19 End Set 20 End Property 21 22 Public Sub New () 23 Dim newNode As New Node 24 newNode.Text = " 新主题 " 25 newNode.Top = 100 26 newNode.Left = 50 27 newNode.ZIndex = 0 28 newNode.Background = Brushes.LightCoral 29 NodeList.Add(newNode) 30 Dim node2 As New Node 31 node2.Text = " 副本 " 32 node2.Left = 200 33 node2.Top = 100 34 node2.ZIndex = 0 35 node2.Background = Brushes.Black 36 NodeList.Add(node2) 37 Dim node3 As New Node 38 With node3 39 .Text = " 副本 " 40 .Left = 300 41 .Top = 100 42 .ZIndex = 0 43 .Background = Brushes.Black 44 End With 45 46 NodeList.Add(node3) 47 End Sub 48 49 End Class 50
看看MainWindow的代码吧
先是Xaml
1 < Window x:Class ="MainWindow" x:Name ="mainWindow" 2 xmlns ="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:ct ="clr-namespace:AMindMapControls;assembly=AMindMapControls" 5 Title ="MainWindow" Height ="350" Width ="525" > 6 < Grid Background ="Transparent" > 7 < ItemsControl Name ="NodeLayer" ItemsSource =" {Binding NodeList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged} " > 8 < ItemsControl.Template > 9 < ControlTemplate TargetType =" {x:Type ItemsControl} " > 10 < ItemsPresenter /> 11 </ ControlTemplate > 12 </ ItemsControl.Template > 13 < ItemsControl.ItemContainerStyle > 14 < Style TargetType =" {x:Type ContentPresenter} " > 15 < Setter Property ="Canvas.Left" Value =" {Binding Path=Left} " ></ Setter > 16 < Setter Property ="Canvas.Top" Value =" {Binding Path=Top} " ></ Setter > 17 < Setter Property ="Canvas.ZIndex" Value =" {Binding Path=ZIndex} " ></ Setter > 18 </ Style > 19 </ ItemsControl.ItemContainerStyle > 20 < ItemsControl.ItemTemplate > 21 < DataTemplate > 22 < ct:ANode x:Name ="Node" Text =" {Binding Text} " 23 OnDragStart ="ANode_OnDragStart" 24 OnDragEnd ="ANode_OnDragEnd" 25 ZIndex =" {Binding ZIndex, Mode=OneWayToSource} " 26 BackgroundColor =" {Binding Background} " 27 > 28 29 </ ct:ANode > 30 </ DataTemplate > 31 </ ItemsControl.ItemTemplate > 32 < ItemsControl.ItemsPanel > 33 < ItemsPanelTemplate > 34 < Canvas Name ="NodeCanvas" Background ="Transparent" 35 MouseMove ="NodeCanvas_MouseMove" 36 MouseDown ="NodeCanvas_MouseDown" > 37 </ Canvas > 38 </ ItemsPanelTemplate > 39 </ ItemsControl.ItemsPanel > 40 </ ItemsControl > 41 </ Grid > 42 </ Window > 43 Canvas是一个方便通过坐标来控制控件位置的容器,所以,像MindMap还是最好用Canvas
来实现,但是问题就来了,Canvas并不能绑定列表或表格形式的数据。而我们的Node是
列表形式的,也是为了方便存储。所以采用ItemsContorl来实现Canvas的列表数据绑定,
为什么要这么做呢,首先,从操作上看,ANode的个数不是固定的,而且会反复的增加和减少
如果自己管理ANode的增加和减少的话,将是不胜其烦的事情,而列表绑定,就变得非常轻松
了,只要对后台模型进行操作,即可实现UI层的对象的增添。也简化每个ANode对应后台的
绑定过程,像我这种懒人,简直是不二之选,哪怕打破UI和Model的松耦合,实际上,目前来看
处理有一个迂回的绑定(详见另一Post ),也没有其他的问题。
分解一下,MainWindow.Xaml
最外层Grid,背景被设置为Transparent,如果不设置任何背景色,将无法获得鼠标事件的支持。
我们还要拖动ANode。接下来是ItemsContorl,对于ItemsContorl和Canvas的关系,当然还有
ANode,请参考另三个Pos (2)(3)
好吧,MainWindow.Xaml没什么好讲的了。
看看后台
1 Imports AMindMapControls 2 3 Class MainWindow 4 Private vm As New MainWindowViewModel 5 6 Public Sub New () 7 8 ' 此调用是设计器所必需的。 9 InitializeComponent() 10 11 ' 在 InitializeComponent() 调用之后添加任何初始化。 12 Me .DataContext = vm 13 End Sub 14 15 #Region " 拖放 " 16 Private DragPoint As Point 17 Private DragNode As ANode 18 19 Private Sub NodeCanvas_MouseMove( ByVal sender As System.Object, ByVal e As System.Windows.Input.MouseEventArgs) 20 If DragNode Is Nothing Then Exit Sub 21 With CType (DragNode.DataContext, Node) 22 .Left = e.GetPosition(sender).X - DragPoint.X 23 .Top = e.GetPosition(sender).Y - DragPoint.Y 24 End With 25 End Sub 26 27 Private Sub ANode_OnDragStart( ByVal sender As AMindMapControls.ANode, ByVal p As Point) 28 DragPoint = p 29 DragNode = sender 30 End Sub 31 32 Private Sub ANode_OnDragEnd( ByVal sender As AMindMapControls.ANode) 33 DragNode = Nothing 34 End Sub 35 #End Region 36 37 Private Sub NodeCanvas_MouseDown( ByVal sender As System.Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) 38 ANode.SetAllNormal() 39 End Sub 40 41 End Class 42 Wpf的事件传递 真是太复杂了,所以,用OnDragStart和OnDragEnd事件来标记拖拽的开始
和结束,并且在界面空白处按下鼠标,取消所有ANode的活动状态,测试完成,效果很理想
下期预告,为 脑图节点,建立后台模型和对模型进行基本管理。其实Node就是模型。