用Windows 8.1的童鞋應該知道OneNote裡面有一個RadialMenu。如下圖,下圖是WIn10應用Drawboard PDF的RadialMenu,Win8.1的機器不好找了。哈哈,由於整個文章比較長,大家可以放《給我一首歌的時間》 邊聽邊看。<滑稽> 從設計到開發包括修複一些bug, ...
用Windows 8.1的童鞋應該知道OneNote裡面有一個RadialMenu。如下圖,下圖是WIn10應用Drawboard PDF的RadialMenu,Win8.1的機器不好找了。哈哈,由於整個文章比較長,大家可以放《給我一首歌的時間》 邊聽邊看。<滑稽>
從設計到開發包括修複一些bug,大概用了不連續的2個月,想看源代碼的童鞋可以先到 RadialMenu 查看效果和代碼。
先放上項目裡面的最終效果
下麵說下整個的過程
1.佈局
首先,可以看到這個控制項一個圓盤形狀的東東,在點擊子菜單的時候,菜單變化,並且帶有duangduangduang的特效。
先來看看RadialMenu的ControlTemplate
<ControlTemplate TargetType="local:RadialMenu"> <!--<Popup x:Name="Popup" IsLightDismissEnabled="False" IsOpen="{TemplateBinding IsOpen}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">--> <Grid x:Name="Root" Width="{TemplateBinding Width}" Height="{TemplateBinding Height}"> <Grid x:Name="ContentGrid"> <Ellipse Fill="{TemplateBinding Background}" StrokeThickness="{TemplateBinding ExpandAreaThickness}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"/> <local:RadialMenuItemsPresenter x:Name="CurrentItemPresenter" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding CurrentItem.Items,RelativeSource={RelativeSource TemplatedParent}}" > <local:RadialMenuItemsPresenter.ItemsPanel> <ItemsPanelTemplate> <local:RadialMenuPanel/> </ItemsPanelTemplate> </local:RadialMenuItemsPresenter.ItemsPanel> </local:RadialMenuItemsPresenter> </Grid> <local:RadialMenuNavigationButton x:Name="NavigationButton" Width="{TemplateBinding RadialMenuNavigationButtonSize}" Height="{TemplateBinding RadialMenuNavigationButtonSize}" VerticalAlignment="Center" HorizontalAlignment="Center"/> </Grid> <!--</Popup>--> </ControlTemplate>View Code
一共三個東西,
1)Ellipse -整個RadialMenu的外形以及背景
2)RadialMenuItemsPresenter-用來展示各個菜單
3)RadialMenuNavigationButton-中間那個導航的按鈕
1,3應該比較好理解,大家看看代碼就能明白。
我著重講下2,這個也是開發一個自定義控制項比較重要的操作,就是知道怎麼按自己的想法去佈局。
RadialMenuItemsPresenter 繼承ItemsControl,它的ItemsPanel是RadialMenuPanel。
<local:RadialMenuItemsPresenter x:Name="CurrentItemPresenter" ItemsSource="{Binding CurrentItem.Items,RelativeSource={RelativeSource TemplatedParent}}" > <local:RadialMenuItemsPresenter.ItemsPanel> <ItemsPanelTemplate> <local:RadialMenuPanel/> </ItemsPanelTemplate> </local:RadialMenuItemsPresenter.ItemsPanel> </local:RadialMenuItemsPresenter>
RadialMenuPanel 就是我們最重要控制怎麼展示RadialMenuItemsPresenter 的Items。
重寫過的Panel 同學一定知道MeasureOverride ,ArrangeOverride 這2個東西。如果不清楚的一定要去看看葡萄城控制項技術團隊的2篇文章 Measure,Arrange。看過之後你將對這2個方法有新的瞭解,對控制項的佈局更加清晰。
下麵請看我優(醜)美(陋)的手畫圖
A菜單將它Arrange到圖中位置,B菜單也放到同樣的位置,但是給它做一定的旋轉。按做這種原理,把全部的菜單都Arrange到控制項的中上位置,並且給他們都做一定的旋轉,這樣就組成了整個的圓弧,這個遞增的角度就是 360°除以菜單的個數。
具體的代碼,請看ArrangeChildren方法。
2.每個菜單的設計
RadialMenuItem -最基礎的顯示文字,圖片等等
RadialColorMenuItem-用於顯示顏色
RadialNumericMenuItem-用於顯示數字,它的子集是RadialNumericMenuChildrenItem,是一個內部的類。
下來還是從我優(醜)美(陋)的手畫圖開始介紹結構吧
藍色的線是整個item的最外邊,黃色是展開子菜單的按鈕的中線。
從藍色到紅色部分就是整個展開子菜單按鈕。 就是圖中的藍色的那個部分。
那麼我們如何來做出這種效果呢。這裡我們要用到Path。
可以看到我們只需要把Path的StorkeThickness設置為展開按鈕的寬,而Path的位置為我們手繪圖中的黃色那條線。寬為藍色到紅色線之間。就可以了。代碼中具體在RadialMenuPanel.cs 中的169行。就是整個圓的半徑減去展開區域的寬度的一半。
var expandAreaRadius = radius - Menu.ExpandAreaThickness / 2.0;
這個半徑就是ArcSegment的Size,那麼StartPoint和ArcSegment的Point怎麼計算呢。
再來上我的手繪圖,這次真是手繪的了。。
如上圖,知道了半徑,可以看到裡面有個直角三角形,一個邊是r,一個邊是x,一個邊是y,由於我們可以算出每個item占的角度,那麼這個直角三角形的角就等於item 角度的一半,使用勾股定理(話說最近項目裡面還用到很多幾何知識,還好沒有把知識還老師)可以算出x,y。
那麼StartPoint= (item的寬度-x,item的高度-y)。因為另一個點是對稱的。。不能算出Point=(item的寬度+x,item的高度-y)
那麼我們就可以畫出這個圓弧了。。
同理RadialColorMenuItem ,那個色塊跟擴展子菜單按鈕部分一樣。在代碼中具體在RadialMenuPanel.cs 中的175-177行
var colorElementStrokeThickness = radius - Menu.ExpandAreaThickness - Menu.SelectedElementThickness - navigationButtonSize * 0.5; var colorElementRadius = radius - Menu.ExpandAreaThickness - Menu.SelectedElementThickness - colorElementStrokeThickness / 2.0;
這裡說明下,色塊跟擴展子菜單按鈕部分之間還有一個選中效果的色塊。
RadialNumericMenuItem
它的子集是RadialNumericMenuChildrenItem
RadialNumericMenuChildrenItem 的選中效果和滑鼠懸停效果是2條Line,代碼中具體在RadialMenuPanel.cs 中的180-197行
3.動畫效果
這篇是Composition API,當然要用Composition API來做動畫呢。。哈哈。
先來說展開/收縮的動畫
_contentGridVisual = ElementCompositionPreview.GetElementVisual(_contentGrid); _compositor = _contentGridVisual.Compositor; rotationAnimation = _compositor.CreateScalarKeyFrameAnimation(); scaleAnimation = _compositor.CreateVector3KeyFrameAnimation(); var easing = _compositor.CreateLinearEasingFunction(); _contentGrid.SizeChanged += (s, e) => { _contentGridVisual.CenterPoint = new Vector3((float)_contentGrid.ActualWidth / 2.0f, (float)_contentGrid.ActualHeight / 2.0f, 0); }; scaleAnimation.InsertKeyFrame(0.0f, new Vector3() { X = 0.0f, Y = 0.0f, Z = 0.0f }); scaleAnimation.InsertKeyFrame(1.0f, new Vector3() { X = 1.0f, Y = 1.0f, Z = 0.0f }, easing); rotationAnimation.InsertKeyFrame(0.0f, -90.0f); rotationAnimation.InsertKeyFrame(1.0f, 0.0f, easing);
可以看到我準備2個動畫,一個是旋轉一個縮小放大,而他們的作用題是RadialMenu的中ContentGrid(不包括中心的那個導航按鈕)
在10586版本之上,動畫有Direction這個屬性,可以反轉動畫效果,也就是說,我這裡寫2個動畫,就可以做到展開/收縮
有沒有覺得很簡單。。為啥這裡特別提10586呢。因為後面有坑。
再說說切換菜單效果,就是點了某個子菜單的展開按鈕,切到它對應的菜單去的效果。其實更簡單,直接用上面的scaleAnimation就可以。不一樣的是我們使用了下麵的代碼來讓菜單先收,再開。
var batch = _compositor.GetCommitBatch(CompositionBatchTypes.Animation); batch.Completed += (s, e) => { SetCurrentItemIn(currentItem); scaleAnimation.Duration = TimeSpan.FromSeconds(0.1); if (!lowerThan14393) { scaleAnimation.Direction = AnimationDirection.Normal; } _contentGridVisual.StartAnimation(nameof(_contentGridVisual.Scale), scaleAnimation); }; scaleAnimation.Duration = TimeSpan.FromSeconds(0.07); scaleAnimation.Direction = AnimationDirection.Reverse; _contentGridVisual.StartAnimation(nameof(_contentGridVisual.Scale), scaleAnimation);
CompositionCommitBatch 有一個completed事件,這個就是動畫結束時候觸發的事件。
另外發現CompositionScopedBatch 也completed事件。
先看看官方介紹CompositionCommitBatch, CompositionScopedBatch
public void BatchAnimations() { // Create a Scoped batch to capture animation completion events _batch = _compositor.CreateScopedBatch(CompositionBatchTypes.Animation); // Executing the Offset animation and aggregating completion event ApplyOffsetAnimation(_greenSquare); // Suspending to exclude the following Rotation animation from the batch _batch.Suspend(); // Executing the Rotation animation ApplyRotationAnimation(_greenSquare); // Resuming the batch to collect additional animations _batch.Resume(); // Executing the Opacity animation and aggregating completion event ApplyOpacityAnimation(_greenSquare); // Batch is ended and no objects can be added _batch.End(); // Method triggered when batch completion event fires _batch.Completed += OnBatchCompleted; }
可以看到CompositionScopedBatch 多了3個方法Resume,Suspend,End,也就是說你可以暫停動畫,並且在這個時間段加入新的動畫到這個group裡面,然後啟動,最後每個動畫完成的時候都會觸發complete。
最後是整個RadialMenu的交互。就是移動動畫。代碼太簡單了。
_radialMenuVisual = ElementCompositionPreview.GetElementVisual(this); _radialMenuVisual.Offset = Offset;
移動的時候處理_radialMenuVisual的Offset就可以了。。哈哈。(註意我沒有把RadialIMenu的元素放在一個Popup裡面,原因是需要滿足,點擊其他地方的時候不要關閉RadialMenu。如果Popup的IsLightDismissEnabled 的屬性設置為flase的話,其他地方點擊又不會有事件觸發。現在的方案是把這個控制項總是放在最高的level 層)
因為設置了控制項的ManipulationMode
ManipulationMode = ManipulationModes.TranslateX | ManipulationModes.TranslateY | ManipulationModes.TranslateInertia;
那麼我們可以在OnManipulationDelta 方法中去改變Offset。達到移動整個控制項的目的
protected override void OnManipulationDelta(ManipulationDeltaRoutedEventArgs e) { UpdateOffset(e.Delta.Translation.X, e.Delta.Translation.Y, e.IsInertial); }
這裡當有慣性的時候我做了反彈效果,具體的代碼在RadilaMenu的BounceOffset 方法中。
不要以為這樣控制項就搞定了,坑坑坑。在等著你。。
第一個地方:
之前講了在10586上面。沒有反轉動畫這個屬性,而且我發現10586上面動畫簡直是垃圾。各種掉幀,各種失效,但是項目裡面要支持10586,那我們怎麼辦呢,嗯。別忘記了我們還有Storyboard。我們需要4個動畫
//use animation for lower than 14393 Storyboard expand; Storyboard collapse; Storyboard open; Storyboard close;
準備動畫代碼如下,說實話,確實有點麻煩難懂,這就是我為啥一直推薦使用Composition API的其中一個原因吧。
_contentGrid.RenderTransformOrigin = new Point(0.5, 0.5); _contentGrid.RenderTransform = new CompositeTransform(); #region expand expand = new Storyboard(); var duk = new DoubleAnimationUsingKeyFrames(); Storyboard.SetTarget(duk, _contentGrid); Storyboard.SetTargetProperty(duk, "(UIElement.RenderTransform).(CompositeTransform.ScaleX)"); duk.KeyFrames.Add(new EasingDoubleKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0)), Value = 0 }); duk.KeyFrames.Add(new EasingDoubleKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0.2)), Value = 1, EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut } }); expand.Children.Add(duk); duk = new DoubleAnimationUsingKeyFrames(); Storyboard.SetTarget(duk, _contentGrid); Storyboard.SetTargetProperty(duk, "(UIElement.RenderTransform).(CompositeTransform.ScaleY)"); duk.KeyFrames.Add(new EasingDoubleKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0)), Value = 0 }); duk.KeyFrames.Add(new EasingDoubleKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0.2)), Value = 1, EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut } }); expand.Children.Add(duk); duk = new DoubleAnimationUsingKeyFrames(); Storyboard.SetTarget(duk, _contentGrid); Storyboard.SetTargetProperty(duk, "(UIElement.RenderTransform).(CompositeTransform.Rotation)"); duk.KeyFrames.Add(new EasingDoubleKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0)), Value = -90 }); duk.KeyFrames.Add(new EasingDoubleKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0.2)), Value = 0, EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut } }); expand.Children.Add(duk); #endregion #region collapse collapse = new Storyboard(); duk = new DoubleAnimationUsingKeyFrames(); Storyboard.SetTarget(duk, _contentGrid); Storyboard.SetTargetProperty(duk, "(UIElement.RenderTransform).(CompositeTransform.ScaleX)"); duk.KeyFrames.Add(new EasingDoubleKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0)), Value = 1 }); duk.KeyFrames.Add(new EasingDoubleKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0.2)), Value = 0, EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut } }); collapse.Children.Add(duk); duk = new DoubleAnimationUsingKeyFrames(); Storyboard.SetTarget(duk, _contentGrid); Storyboard.SetTargetProperty(duk, "(UIElement.RenderTransform).(CompositeTransform.ScaleY)"); duk.KeyFrames.Add(new EasingDoubleKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0)), Value = 1 }); duk.KeyFrames.Add(new EasingDoubleKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0.2)), Value = 0, EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut } }); collapse.Children.Add(duk); duk = new DoubleAnimationUsingKeyFrames(); Storyboard.SetTarget(duk, _contentGrid); Storyboard.SetTargetProperty(duk, "(UIElement.RenderTransform).(CompositeTransform.Rotation)"); duk.KeyFrames.Add(new EasingDoubleKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0)), Value = 0 }); duk.KeyFrames.Add(new EasingDoubleKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0.2)), Value = -90, EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut } }); collapse.Children.Add(duk); #endregion #region Open open = new Storyboard(); duk = new DoubleAnimationUsingKeyFrames(); Storyboard.SetTarget(duk, _contentGrid); Storyboard.SetTargetProperty(duk, "(UIElement.RenderTransform).(CompositeTransform.ScaleX)"); duk.KeyFrames.Add(new EasingDoubleKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0)), Value = 0 }); duk.KeyFrames.Add(new EasingDoubleKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0.1)), Value = 1, EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut } }); open.Children.Add(duk); duk = new DoubleAnimationUsingKeyFrames(); Storyboard.SetTarget(duk, _contentGrid); Storyboard.SetTargetProperty(duk, "(UIElement.RenderTransform).(CompositeTransform.ScaleY)"); duk.KeyFrames.Add(new EasingDoubleKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0)), Value = 0 }); duk.KeyFrames.Add(new EasingDoubleKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0.1)), Value = 1, EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut } }); open.Children.Add(duk); #endregion #region Close close = new Storyboard(); duk = new DoubleAnimationUsingKeyFrames(); Storyboard.SetTarget(duk, _contentGrid); Storyboard.SetTargetProperty(duk, "(UIElement.RenderTransform).(CompositeTransform.ScaleX)"); duk.KeyFrames.Add(new EasingDoubleKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0)), Value = 1 }); duk.KeyFrames.Add(new EasingDoubleKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0.07)), Value = 0, EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut } }); close.Children.Add(duk); duk = new DoubleAnimationUsingKeyFrames(); Storyboard.SetTarget(duk, _contentGrid); Storyboard.SetTargetProperty(duk, "(UIElement.RenderTransform).(CompositeTransform.ScaleY)"); duk.KeyFrames.Add(new EasingDoubleKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0)), Value = 1 }); duk.KeyFrames.Add(new EasingDoubleKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0.07)), Value = 0, EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut } }); close.Children.Add(duk); #endregionView Code
第二個地方:
為了實現RadialNumericMenuChildrenItem 第一個和最後一個Item缺掉一個口的效果,如下圖
我為這個Path做了一個Clip。問題在於15063版本上面Path的Clip是針對它實際圖形位置,而不是Path實際的大小(從之前手繪圖看錯,實際大小是那個外面的矩形,而且它繪畫的圖像是小於它的)。所以做了下麵的特殊處理
var Build16229 = ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 5); // var Build15063 = ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 4); //var Build14393 = ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 3); //var Build10586 = ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 2); bool is15063 = Build15063 && !Build16229; if (!is15063) { if (k == 0) { radialNumericMenuChildrenItem.ColorElement.Clip = new RectangleGeometry() { Rect = new Rect(sectorRect.Width / 2.0, 0, sectorRect.Width / 2.0, sectorRect.Height) }; } else { radialNumericMenuChildrenItem.ColorElement.Clip = new RectangleGeometry() { Rect = new Rect(0, 0, sectorRect.Width / 2.0, sectorRect.Height) }; } } else { if (k == 0) { radialNumericMenuChildrenItem.ColorElement.Clip = new RectangleGeometry() { Rect = new Rect((colorElement.EndPoint.X - colorElement.StartPoint.X) / 2.0, 0, colorElement.EndPoint.X - colorElement.StartPoint.X, colorElement.Size.Height) }; } else { radialNumericMenuChildrenItem.ColorElement.Clip = new RectangleGeometry() { Rect = new Rect(0, 0, (colorElement.EndPoint.X - colorElement.StartPoint.X) / 2.0, colorElement.Size.Height) }; } }View Code
第三個地方:
RadialNumericMenuItem 的Items之前設計是使用New關鍵字直接重寫了子類的Items(RadialMenuItemCollection),為ObservableCollection<double>
在10586 release 模式上面RadialMenuPanel 的Children.Count會為一個奇怪的負數,而不是0.
修改方案之後 增加了一個ObservableCollection<double> NumericItems 屬性來設置數
並且使用 [ContentProperty(Name = "NumericItems")] 屬性標簽覆蓋子類,
但是哇在最新的SDK上面又有問題了。。debug模式下編譯能過。但是運行起來ContentProperty 沒有生效不通過。
最後也沒啥辦法了。。那就是在xaml裡面老實實 寫NumericItems等於啥吧。。( ╯□╰ )
第四個地方
居然是virtual 的Offset 在Page的 NavigationCacheMode 等於enable 或者request的時候。有大大大的問題啊。微軟來背鍋啊。
問題是這樣的。Radilamenu放在一個Page NavigationCacheMode =Enable的頁面上。。然後跳轉到其他頁面再跳轉回來的時候。
Virtual.Offset 的還是原來的值。而且整個RadialMenu看起來也像在原來的位置。。但是其實你用滑鼠或者觸摸的時候。發現RadialMenu觸發事件的位置居然是在左上角。就是說是Offset為0的時候那個區域。。不管用了什麼辦法。還是無解。
最後只能放棄使用Virtual.Offset ,然後改為控制控制項Popup的HorizontalOffset和VerticalOffset來移動。
第五個地方:
之前Popup放ControlTemplate裡面,這會導致RadilamMenu只是在你放那個父容器那一層是最上層。而一般都需要RadialMenu在整個應用的最上層。
辦法只有一個不要將Popup放進去virtual tree,而且是在代碼中new 一個出來,這樣微軟會把這個Popup的Child放進PopupRoot,這個東東是整個應用的最上層,如下圖。
PopupRoot為應用的最上層
RootScrollViewer-ScrollContentPresenter-Border-Frame-ContentPresenter 這裡就是平時大家熟悉的MainPage
不知道咋哪裡查看這個的。請打開VS,運行UWP程式,看到中間那坨黑色的地方嗎,點第一個VS的左邊就會出現Live Vistual Tree,通過這個你能查看到結構,某個控制項的當前屬性,或者設置它的屬性,反正很叼了。學習控制項的孩子一定要搞懂哈。
註意不要將RadilaMenu直接放進Virtual tree,因為RadialMenu是準備放在new 的Popup裡面的,大家都知道一個UIElement不能賦值給2個Parent的。
那麼告訴大家一個技巧。可以用AttachedProperty將RadialMenu加入到Xaml但是不放進Virtual tree。這樣我們就能在Xaml中開心的寫了。
如Sample 中RadialMenuBase 類的AttachedMenu屬性。
<radialMenu:RadialMenuBase.AttachedMenu> <radialMenu:RadialMenu x:Name="radialMenu" Offset="100,100" SectorCount="8" IsExpanded="False"> <radialMenu:RadialMenuItem Content="Color" ToolTip="Color"> <radialMenu:RadialColorMenuItem Color="Red"/> </radialMenu:RadialMenuItem> <radialMenu:RadialNumericMenuItem x:Name="radialNumericMenuItem" Value="6"> <radialMenu:RadialNumericMenuItem.Content> <TextBlock> <Run Text="Fontsize"/> <Run Text="("/> <Run Text="{Binding Value,ElementName=radialNumericMenuItem}"/> <Run Text=")"/> </TextBlock> </radialMenu:RadialNumericMenuItem.Content> <radialMenu:RadialNumericMenuItem.NumericItems> <x:Double>1</x:Double> <x:Double>2</x:Double> <x:Double>3</x:Double> <x:Double>4</x:Double> <x:Double>5</x:Double> <x:Double>6</x:Double> <x:Double>7</x:Double> <x:Double>8</x:Double> <x:Double>9</x:Double> <x:Double>10</x:Double> <x:Double>11</x:Double> <x:Double>12</x:Double> <x:Double>13</x:Double> <x:Double>14</x:Double> <x:Double>15</x:Double> <x:Double>16</x:Double> </radialMenu:RadialNumericMenuItem.NumericItems> </radialMenu:RadialNumericMenuItem> <radialMenu:RadialMenuItem Content="Disabled" IsEnabled="True"> <radialMenu:RadialMenuItem Content="test"/> </radialMenu:RadialMenuItem> </radialMenu:RadialMenu> </radialMenu:RadialMenuBase.AttachedMenu>
最後最後,這個控制項裡面用到一些屬性標簽 比如
[ContentProperty(Name = "NumericItems")]
[EditorBrowsable(EditorBrowsableState.Never)]
[Browsable(false)]
這些東西跟Design Time有很大大的關係,就是你平時寫在Xaml裡面的東東。要是有不清楚的。可以留言或者私信我,歡迎投雞蛋。
最後想說。要做好一個控制項這些是遠遠不夠的,還會涉及控制項的 designtime,各種邏輯,各種版本適配,各種擴展,一個好的控制項絕對等同一個複雜的項目。我喜歡做控制項。希望給大家使用降低學習成本。
謝謝堅持看完的童鞋,整個控制項研發的過程遠遠不止這篇文章,歡迎提問。
老規矩 開源有益:RadialMenu
想在10240上面使用這個控制項的童鞋,可以把關於Composition API的代碼都註釋掉。使用StoryBoard來做動畫就ok了。(是不是感覺我強行為Composition API 打Call,哈哈哈)