一路下来,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 WPF 苦逼的迂回绑定),也没有其他的问题。
分解一下,MainWindow.Xaml
最外层Grid,背景被设置为Transparent,如果不设置任何背景色,将无法获得鼠标事件的支持。
我们还要拖动ANode。接下来是ItemsContorl,对于ItemsContorl和Canvas的关系,当然还有
ANode,请参考另三个Pos 将ObservableCollection(Of T) 数据 绑定到 Canvas (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就是模型。