using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.ComponentModel; namespace ZdfFlatUI { public class TimelineItem : ContentControl { #region DependencyProperty #region IsFirstItem /// <summary> /// 獲取或者設置該項在列表中是不是第一個 /// </summary> [Bindable(true), Description("獲取或者設置該項在列表中是不是第一個")] public bool IsFirstItem { get { return (bool)GetValue(IsFirstItemProperty); } set { SetValue(IsFirstItemProperty, value); } } public static readonly DependencyProperty IsFirstItemProperty = DependencyProperty.Register("IsFirstItem", typeof(bool), typeof(TimelineItem), new PropertyMetadata(false)); #endregion #region IsMiddleItem /// <summary> /// 獲取或者設置該項在列表中是不是中間的一個 /// </summary> [Bindable(true), Description("獲取或者設置該項在列表中是不是中間的一個")] public bool IsMiddleItem { get { return (bool)GetValue(IsMiddleItemProperty); } set { SetValue(IsMiddleItemProperty, value); } } public static readonly DependencyProperty IsMiddleItemProperty = DependencyProperty.Register("IsMiddleItem", typeof(bool), typeof(TimelineItem), new PropertyMetadata(false)); #endregion #region IsLastItem /// <summary> /// 獲取或者設置該項在列表中是不是最後一個 /// </summary> [Bindable(true), Description("獲取或者設置該項在列表中是不是最後一個")] public bool IsLastItem { get { return (bool)GetValue(IsLastItemProperty); } set { SetValue(IsLastItemProperty, value); } } public static readonly DependencyProperty IsLastItemProperty = DependencyProperty.Register("IsLastItem", typeof(bool), typeof(TimelineItem), new PropertyMetadata(false)); #endregion #endregion #region Constructors static TimelineItem() { DefaultStyleKeyProperty.OverrideMetadata(typeof(TimelineItem), new FrameworkPropertyMetadata(typeof(TimelineItem))); } #endregion } }
Timeline代碼git
1 using System; 2 using System.Collections.Generic; 3 using System.Collections.Specialized; 4 using System.Linq; 5 using System.Text; 6 using System.Windows; 7 using System.Windows.Controls; 8 using System.ComponentModel; 9 namespace ZdfFlatUI 10 { 11 /// <summary> 12 /// 時間軸 13 /// </summary> 14 /// <remarks>add by zhidanfeng 2017.5.29</remarks> 15 public class Timeline : ItemsControl 16 { 17 #region private fields 18 #endregion 19 #region DependencyProperty 20 #region FirstSlotTemplate 21 /// <summary> 22 /// 獲取或者設置第一個時間軸點的樣子 23 /// </summary> 24 [Bindable(true), Description("獲取或者設置第一個時間軸點的樣子")] 25 public DataTemplate FirstSlotTemplate 26 { 27 get { return (DataTemplate)GetValue(FirstSlotTemplateProperty); } 28 set { SetValue(FirstSlotTemplateProperty, value); } 29 } 30 31 public static readonly DependencyProperty FirstSlotTemplateProperty = 32 DependencyProperty.Register("FirstSlotTemplate", typeof(DataTemplate), typeof(Timeline)); 33 #endregion 34 #region MiddleSlotTemplate 35 /// <summary> 36 /// 獲取或者設置中間的時間軸點的樣子 37 /// </summary> 38 [Bindable(true), Description("獲取或者設置中間的時間軸點的樣子")] 39 public DataTemplate MiddleSlotTemplate 40 { 41 get { return (DataTemplate)GetValue(MiddleSlotTemplateProperty); } 42 set { SetValue(MiddleSlotTemplateProperty, value); } 43 } 44 45 public static readonly DependencyProperty MiddleSlotTemplateProperty = 46 DependencyProperty.Register("MiddleSlotTemplate", typeof(DataTemplate), typeof(Timeline)); 47 #endregion 48 #region LastItemTemplate 49 /// <summary> 50 /// 獲取或者設置最後一個時間軸點的樣子 51 /// </summary> 52 [Bindable(true), Description("獲取或者設置最後一個時間軸點的樣子")] 53 public DataTemplate LastSlotTemplate 54 { 55 get { return (DataTemplate)GetValue(LastSlotTemplateProperty); } 56 set { SetValue(LastSlotTemplateProperty, value); } 57 } 58 59 public static readonly DependencyProperty LastSlotTemplateProperty = 60 DependencyProperty.Register("LastSlotTemplate", typeof(DataTemplate), typeof(Timeline)); 61 #endregion 62 #region IsCustomEverySlot 63 /// <summary> 64 /// 獲取或者設置是否自定義每個時間軸點的外觀。 65 /// </summary> 66 [Bindable(true), Description("獲取或者設置是否自定義每個時間軸點的外觀。當屬性值爲True時,FirstSlotTemplate、MiddleSlotTemplate、LastSlotTemplate屬性都將失效,只能設置SlotTemplate來定義每個時間軸點的樣式")] 67 public bool IsCustomEverySlot 68 { 69 get { return (bool)GetValue(IsCustomEverySlotProperty); } 70 set { SetValue(IsCustomEverySlotProperty, value); } 71 } 72 73 public static readonly DependencyProperty IsCustomEverySlotProperty = 74 DependencyProperty.Register("IsCustomEverySlot", typeof(bool), typeof(Timeline), new PropertyMetadata(false)); 75 #endregion 76 #region SlotTemplate 77 /// <summary> 78 /// 獲取或者設置每一個時間軸點的外觀 79 /// </summary> 80 [Bindable(true), Description("獲取或者設置每一個時間軸點的外觀。只有當IsCustomEverySlot屬性爲True時,該屬性才生效")] 81 public DataTemplate SlotTemplate 82 { 83 get { return (DataTemplate)GetValue(SlotTemplateProperty); } 84 set { SetValue(SlotTemplateProperty, value); } 85 } 86 87 public static readonly DependencyProperty SlotTemplateProperty = 88 DependencyProperty.Register("SlotTemplate", typeof(DataTemplate), typeof(Timeline)); 89 #endregion 90 #endregion 91 #region Constructors 92 static Timeline() 93 { 94 DefaultStyleKeyProperty.OverrideMetadata(typeof(Timeline), new FrameworkPropertyMetadata(typeof(Timeline))); 95 } 96 #endregion 97 #region Override 98 protected override void PrepareContainerForItemOverride(DependencyObject element, object item) 99 { 100 int index = this.ItemContainerGenerator.IndexFromContainer(element); 101 TimelineItem timelineItem = element as TimelineItem; 102 if(timelineItem == null) 103 { 104 return; 105 } 106 if(index == 0) 107 { 108 timelineItem.IsFirstItem = true; 109 } 110 if(index == this.Items.Count - 1) 111 { 112 timelineItem.IsLastItem = true; 113 } 114 base.PrepareContainerForItemOverride(timelineItem, item); 115 } 116 protected override DependencyObject GetContainerForItemOverride() 117 { 118 return new TimelineItem(); 119 } 120 public override void OnApplyTemplate() 121 { 122 base.OnApplyTemplate(); 123 } 124 protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) 125 { 126 base.OnItemsChanged(e); 127 //如下代碼是爲了新增項或者移除項時,正確設置每一個Item的外觀 128 switch (e.Action) 129 { 130 case NotifyCollectionChangedAction.Add: 131 if (e.NewStartingIndex == 0) //若是新添加項是放在第一位,則更改原來的第一位的屬性值 132 { 133 this.SetTimelineItem(e.NewStartingIndex + e.NewItems.Count); 134 } 135 //若是新添加項是放在最後一位,則更改原來的最後一位的屬性值 136 if (e.NewStartingIndex == this.Items.Count - e.NewItems.Count) 137 { 138 this.SetTimelineItem(e.NewStartingIndex - 1); 139 } 140 break; 141 case NotifyCollectionChangedAction.Remove: 142 if(e.OldStartingIndex == 0) //若是移除的是第一個,則更改更新後的第一項的屬性值 143 { 144 this.SetTimelineItem(0); 145 } 146 else 147 { 148 this.SetTimelineItem(e.OldStartingIndex - 1); 149 } 150 break; 151 } 152 } 153 #endregion 154 #region private function 155 /// <summary> 156 /// 設置TimelineItem的位置屬性 157 /// </summary> 158 /// <param name="index"></param> 159 private void SetTimelineItem(int index) 160 { 161 if(index > this.Items.Count || index < 0) 162 { 163 return; 164 } 165 TimelineItem timelineItem = this.ItemContainerGenerator.ContainerFromIndex(index) as TimelineItem; 166 if(timelineItem == null) 167 { 168 return; 169 } 170 timelineItem.IsFirstItem = index == 0; 171 timelineItem.IsLastItem = index == this.Items.Count - 1; 172 timelineItem.IsMiddleItem = index > 0 && index < this.Items.Count - 1; 173 } 174 #endregion 175 } 176 }
樣式代碼:github
1 <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 2 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ZUI="clr-namespace:ZdfFlatUI"> 3 <PathGeometry x:Key="Icon_Gou" Figures="M378.410667 850.450963C364.491852 850.450963 350.610963 845.293037 340.02963 834.939259L20.920889 523.529481C-0.279704 502.821926-0.279704 469.295407 20.920889 448.587852 42.121481 427.880296 76.48237 427.880296 97.682963 448.587852L378.410667 722.526815 925.75763 188.491852C946.958222 167.784296 981.319111 167.784296 1002.519704 188.491852 1023.720296 209.161481 1023.720296 242.688 1002.519704 263.395556L416.791704 834.939259C406.172444 845.293037 392.291556 850.450963 378.410667 850.450963L378.410667 850.450963Z" /> 4 <DataTemplate x:Key="FirstSlotTemplate"> 5 <Grid> 6 <Ellipse x:Name="Slot1" Width="15" Height="15" Fill="#30AAADAF" /> 7 <Ellipse x:Name="Slot2" Width="7" Height="7" Fill="#FF6501" /> 8 </Grid> 9 </DataTemplate> 10 <DataTemplate x:Key="LastSlotTemplate"> 11 <Grid> 12 <Ellipse x:Name="Slot1" Width="15" Height="15" Fill="#AAADAF" /> 13 <Path x:Name="path" Width="9" 14 Data="{StaticResource Icon_Gou}" 15 Fill="#FFFFFF" Stretch="Uniform" /> 16 </Grid> 17 </DataTemplate> 18 <Style TargetType="{x:Type ZUI:TimelineItem}"> 19 <Setter Property="Background" Value="Transparent" /> 20 <Setter Property="BorderBrush" Value="{Binding BorderBrush, RelativeSource={RelativeSource AncestorType={x:Type ZUI:Timeline}}}" /> 21 <Setter Property="BorderThickness" Value="0" /> 22 <Setter Property="Foreground" Value="{Binding Foreground, RelativeSource={RelativeSource AncestorType={x:Type ZUI:Timeline}}}" /> 23 <Setter Property="Padding" Value="15,0,15,0" /> 24 <Setter Property="MinHeight" Value="50" /> 25 <Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ZUI:Timeline}}}" /> 26 <Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ZUI:Timeline}}}" /> 27 <Setter Property="SnapsToDevicePixels" Value="True" /> 28 <Setter Property="UseLayoutRounding" Value="True" /> 29 <Setter Property="Template"> 30 <Setter.Value> 31 <ControlTemplate TargetType="{x:Type ZUI:TimelineItem}"> 32 <Grid> 33 <Grid.RowDefinitions> 34 <RowDefinition Height="auto" /> 35 <RowDefinition Height="*" /> 36 </Grid.RowDefinitions> 37 <Grid.ColumnDefinitions> 38 <ColumnDefinition Width="auto" /> 39 <ColumnDefinition Width="*" /> 40 </Grid.ColumnDefinitions> 41 <ContentPresenter x:Name="Slot" ContentTemplate="{Binding MiddleSlotTemplate, RelativeSource={RelativeSource AncestorType={x:Type ZUI:Timeline}}}" /> 42 <Rectangle x:Name="Line" Grid.Row="1" Width="1" 43 Fill="{TemplateBinding BorderBrush}" /> 44 <ContentPresenter Grid.RowSpan="2" Grid.Column="1" 45 Margin="{TemplateBinding Padding}" 46 HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 47 VerticalAlignment="{TemplateBinding VerticalContentAlignment}" /> 48 </Grid> 49 <ControlTemplate.Triggers> 50 <!-- 51 當IsCustomEverySlot爲True時,FirstSlotTemplate、MiddleSlotTemplate、LastSlotTemplate都將失效, 52 只能設置SlotTemplate來定義每個時間軸點的樣式 53 --> 54 <MultiDataTrigger> 55 <MultiDataTrigger.Conditions> 56 <Condition Binding="{Binding IsFirstItem, RelativeSource={RelativeSource Self}}" Value="True" /> 57 <Condition Binding="{Binding IsCustomEverySlot, RelativeSource={RelativeSource AncestorType={x:Type ZUI:Timeline}}}" Value="False" /> 58 </MultiDataTrigger.Conditions> 59 <Setter TargetName="Slot" Property="ContentTemplate" Value="{Binding FirstSlotTemplate, RelativeSource={RelativeSource AncestorType={x:Type ZUI:Timeline}}}" /> 60 </MultiDataTrigger> 61 <MultiDataTrigger> 62 <MultiDataTrigger.Conditions> 63 <Condition Binding="{Binding IsLastItem, RelativeSource={RelativeSource Self}}" Value="True" /> 64 <Condition Binding="{Binding IsCustomEverySlot, RelativeSource={RelativeSource AncestorType={x:Type ZUI:Timeline}}}" Value="False" /> 65 </MultiDataTrigger.Conditions> 66 <Setter TargetName="Slot" Property="ContentTemplate" Value="{Binding LastSlotTemplate, RelativeSource={RelativeSource AncestorType={x:Type ZUI:Timeline}}}" /> 67 </MultiDataTrigger> 68 <MultiDataTrigger> 69 <MultiDataTrigger.Conditions> 70 <Condition Binding="{Binding IsMiddleItem, RelativeSource={RelativeSource Self}}" Value="True" /> 71 <Condition Binding="{Binding IsCustomEverySlot, RelativeSource={RelativeSource AncestorType={x:Type ZUI:Timeline}}}" Value="False" /> 72 </MultiDataTrigger.Conditions> 73 <Setter TargetName="Slot" Property="ContentTemplate" Value="{Binding MiddleSlotTemplate, RelativeSource={RelativeSource AncestorType={x:Type ZUI:Timeline}}}" /> 74 </MultiDataTrigger> 75 <DataTrigger Binding="{Binding IsCustomEverySlot, RelativeSource={RelativeSource AncestorType={x:Type ZUI:Timeline}}}" Value="True"> 76 <Setter TargetName="Slot" Property="ContentTemplate" Value="{Binding SlotTemplate, RelativeSource={RelativeSource AncestorType={x:Type ZUI:Timeline}}}" /> 77 </DataTrigger> 78 <Trigger Property="IsLastItem" Value="True"> 79 <Setter TargetName="Line" Property="Visibility" Value="Collapsed" /> 80 </Trigger> 81 <Trigger Property="IsMiddleItem" Value="True"> 82 <Setter TargetName="Line" Property="Visibility" Value="Visible" /> 83 </Trigger> 84 </ControlTemplate.Triggers> 85 </ControlTemplate> 86 </Setter.Value> 87 </Setter> 88 </Style> 89 <Style TargetType="{x:Type ZUI:Timeline}"> 90 <Setter Property="Background" Value="Transparent" /> 91 <Setter Property="BorderBrush" Value="#F0F0F0" /> 92 <Setter Property="BorderThickness" Value="0" /> 93 <Setter Property="Foreground" Value="Black" /> 94 <Setter Property="HorizontalContentAlignment" Value="Left" /> 95 <Setter Property="VerticalContentAlignment" Value="Top" /> 96 <Setter Property="SnapsToDevicePixels" Value="True" /> 97 <Setter Property="UseLayoutRounding" Value="True" /> 98 <Setter Property="FirstSlotTemplate" Value="{StaticResource FirstSlotTemplate}" /> 99 <Setter Property="MiddleSlotTemplate" Value="{StaticResource LastSlotTemplate}" /> 100 <Setter Property="LastSlotTemplate" Value="{StaticResource LastSlotTemplate}" /> 101 <Setter Property="Template"> 102 <Setter.Value> 103 <ControlTemplate TargetType="{x:Type ZUI:Timeline}"> 104 <Border Background="{TemplateBinding Background}" 105 BorderBrush="{TemplateBinding BorderBrush}" 106 BorderThickness="{TemplateBinding BorderThickness}" 107 SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" 108 UseLayoutRounding="{TemplateBinding UseLayoutRounding}"> 109 <ZUI:ZScrollViewer> 110 <ItemsPresenter /> 111 </ZUI:ZScrollViewer> 112 </Border> 113 </ControlTemplate> 114 </Setter.Value> 115 </Setter> 116 </Style> 117 </ResourceDictionary>
在Timeline類中,有幾處關鍵代碼ide
protected override DependencyObject GetContainerForItemOverride() { return new TimelineItem(); }
②、在控件第一次初始化以及在後來的新增時,須要設置TimelineItem的幾個依賴屬性值,即設置Item具體是第一個、中間項仍是最後一項,所以須要重寫【PrepareContainerForItemOverride】this
protected override void PrepareContainerForItemOverride(DependencyObject element, object item) { int index = this.ItemContainerGenerator.IndexFromContainer(element); TimelineItem timelineItem = element as TimelineItem; if(timelineItem == null) { return; } if(index == 0) { timelineItem.IsFirstItem = true; } if(index == this.Items.Count - 1) { timelineItem.IsLastItem = true; } base.PrepareContainerForItemOverride(timelineItem, item); }
③、Timeline控件在運行過程當中,可能會涉及到新增或者刪除節點,這時一樣須要實時的設置每一個TimelineItem的IsFirstItem、IsMiddleItem、IsLastItem,這樣呈現正確的外觀spa
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) { base.OnItemsChanged(e); //如下代碼是爲了新增項或者移除項時,正確設置每一個Item的外觀 switch (e.Action) { case NotifyCollectionChangedAction.Add: if (e.NewStartingIndex == 0) //若是新添加項是放在第一位,則更改原來的第一位的屬性值 { this.SetTimelineItem(e.NewStartingIndex + e.NewItems.Count); } //若是新添加項是放在最後一位,則更改原來的最後一位的屬性值 if (e.NewStartingIndex == this.Items.Count - e.NewItems.Count) { this.SetTimelineItem(e.NewStartingIndex - 1); } break; case NotifyCollectionChangedAction.Remove: if(e.OldStartingIndex == 0) //若是移除的是第一個,則更改更新後的第一項的屬性值 { this.SetTimelineItem(0); } else { this.SetTimelineItem(e.OldStartingIndex - 1); } break; } }
最終效果圖:設計