PageRenderTime 57ms CodeModel.GetById 34ms RepoModel.GetById 1ms app.codeStats 0ms

/xaml_commanding/CS/DynamicCommandBar.cs

https://gitlab.com/Rockyspade/Windows-universal-samples
C# | 265 lines | 195 code | 41 blank | 29 comment | 46 complexity | d334a7e8c09dc095f261dbd9060902f9 MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. using Windows.Foundation;
  8. using Windows.UI.Xaml;
  9. using Windows.UI.Xaml.Controls;
  10. namespace Commanding
  11. {
  12. /// <summary>
  13. /// We extend the CommandBar by adding logic to reflow commands as space is reduced
  14. /// </summary>
  15. public class DynamicCommandBar : CommandBar
  16. {
  17. public DynamicCommandBar()
  18. {
  19. //this.SizeChanged += CommandBar_SizeChanged;
  20. }
  21. public double ContentMinWidth
  22. {
  23. get { return (double)GetValue(ContentMinWidthProperty); }
  24. set { SetValue(ContentMinWidthProperty, value); }
  25. }
  26. public static readonly DependencyProperty ContentMinWidthProperty =
  27. DependencyProperty.Register("ContentMinWidth", typeof(double), typeof(DynamicCommandBar), new PropertyMetadata(0.0d));
  28. private Button moreButton;
  29. private ItemsControl primaryCommands;
  30. protected override void OnApplyTemplate()
  31. {
  32. base.OnApplyTemplate();
  33. moreButton = this.GetTemplateChild("MoreButton") as Button;
  34. primaryCommands = this.GetTemplateChild("PrimaryItemsControl") as ItemsControl;
  35. }
  36. // Store the item, the width before moving, if it is a separator, and if it appears in the overflow
  37. private Stack<Tuple<ICommandBarElement, double>> overflow = new Stack<Tuple<ICommandBarElement, double>>();
  38. private Queue<Tuple<ICommandBarElement, double>> separatorQueue = new Queue<Tuple<ICommandBarElement, double>>();
  39. private double separatorQueueWidth = 0.0;
  40. protected override Size MeasureOverride(Size availableSize)
  41. {
  42. var sizeToReport = base.MeasureOverride(availableSize);
  43. // Account for the size of the Content area
  44. var contentWidth = this.ContentMinWidth;
  45. // Account for the size of the More button
  46. var expandButtonWidth = 0.0;
  47. if (moreButton != null && moreButton.Visibility == Visibility.Visible)
  48. {
  49. expandButtonWidth = moreButton.DesiredSize.Width;
  50. }
  51. double requestedWidth = expandButtonWidth + contentWidth;
  52. // Include the size of all the PrimaryCommands
  53. foreach (var cmd in this.PrimaryCommands)
  54. {
  55. var uie = cmd as UIElement;
  56. requestedWidth += uie.DesiredSize.Width;
  57. }
  58. // First, move items to the overflow until the remaining PrimaryCommands fit
  59. for (int i = this.PrimaryCommands.Count - 1; i > 0 && requestedWidth > availableSize.Width; i--)
  60. {
  61. var item = this.PrimaryCommands[i];
  62. var element = item as UIElement;
  63. if (element == null)
  64. {
  65. continue;
  66. }
  67. requestedWidth -= element.DesiredSize.Width;
  68. if (this.overflow.Count == 0 && !(element is AppBarSeparator))
  69. {
  70. // Insert a separator to differentiate between the items that were already in the overflow versus
  71. // those we moved
  72. var abs = new AppBarSeparator();
  73. abs.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
  74. this.separatorQueue.Enqueue(new Tuple<ICommandBarElement, double>(abs, abs.DesiredSize.Width));
  75. separatorQueueWidth += abs.DesiredSize.Width;
  76. }
  77. this.PrimaryCommands.RemoveAt(i);
  78. // move separators we've queued up
  79. while (separatorQueue.Count > 0)
  80. {
  81. var next = separatorQueue.Dequeue();
  82. separatorQueueWidth -= next.Item2;
  83. overflow.Push(next);
  84. this.SecondaryCommands.Insert(0, next.Item1);
  85. }
  86. // We store the measured size before it moves to overflow and will rely on that value
  87. // when determining how many items to move back out the overflow.
  88. overflow.Push(new Tuple<ICommandBarElement, double>(item, element.DesiredSize.Width));
  89. this.SecondaryCommands.Insert(0, (ICommandBarElement)element);
  90. // if a separator was adjacent to the one we removed then move the separator to our holding queue so that it doesn't appear without actually separating the content
  91. var last = this.PrimaryCommands.LastOrDefault() as AppBarSeparator;
  92. if (last != null)
  93. {
  94. this.PrimaryCommands.RemoveAt(this.PrimaryCommands.Count - 1);
  95. this.separatorQueue.Enqueue(new Tuple<ICommandBarElement, double>(last, last.DesiredSize.Width));
  96. separatorQueueWidth += last.DesiredSize.Width;
  97. requestedWidth -= last.DesiredSize.Width;
  98. }
  99. }
  100. //else if (requestedWidth < availableSize.Width) // Check if we have room to move items out of the overflow
  101. //{
  102. // Next move items out of the overflow if room is available
  103. while (overflow.Count > 0 && requestedWidth + separatorQueueWidth + overflow.Peek().Item2 <= availableSize.Width)
  104. {
  105. var t = overflow.Pop();
  106. this.SecondaryCommands.Remove(t.Item1);
  107. if (this.overflow.Count == 1)
  108. {
  109. overflow.Pop();
  110. this.SecondaryCommands.RemoveAt(0);
  111. }
  112. if (t.Item1 is AppBarSeparator)
  113. {
  114. this.separatorQueue.Enqueue(t);
  115. separatorQueueWidth += t.Item2;
  116. continue;
  117. }
  118. else
  119. {
  120. while (separatorQueue.Count > 0)
  121. {
  122. var next = separatorQueue.Dequeue();
  123. separatorQueueWidth -= next.Item2;
  124. this.PrimaryCommands.Add(next.Item1);
  125. requestedWidth += next.Item2;
  126. }
  127. }
  128. // Sometimes this property is being set to disabled
  129. ((Control)t.Item1).IsEnabled = true;
  130. this.PrimaryCommands.Add(t.Item1);
  131. requestedWidth += t.Item2;
  132. // check to see if after moving this item we leave a separator at the top of the overflow
  133. if (this.overflow.Count > 0)
  134. {
  135. var test = this.overflow.Peek();
  136. if (test.Item1 is AppBarSeparator)
  137. {
  138. // we won't leave an orphaned separator
  139. this.SecondaryCommands.RemoveAt(0);
  140. var top = this.overflow.Pop();
  141. var element = top.Item1 as UIElement;
  142. element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
  143. this.separatorQueue.Enqueue(new Tuple<ICommandBarElement, double>(top.Item1, element.DesiredSize.Width));
  144. separatorQueueWidth += element.DesiredSize.Width;
  145. }
  146. }
  147. }
  148. //}
  149. return sizeToReport;
  150. }
  151. private void CommandBar_SizeChanged(object sender, Windows.UI.Xaml.SizeChangedEventArgs e)
  152. {
  153. // Account for the size of Content
  154. Size unbounded = new Size(double.PositiveInfinity, double.PositiveInfinity);
  155. var contentWidth = 0.0;
  156. if (this.Content != null)
  157. {
  158. contentWidth = this.ContentMinWidth;
  159. }
  160. // Account for the size of the More button
  161. var expandButtonWidth = 0.0;
  162. if (moreButton != null && moreButton.Visibility == Visibility.Visible)
  163. {
  164. moreButton.Measure(unbounded);
  165. expandButtonWidth = moreButton.DesiredSize.Width;
  166. }
  167. double width = expandButtonWidth + contentWidth;
  168. foreach (var cmd in this.PrimaryCommands)
  169. {
  170. var uie = cmd as UIElement;
  171. uie.Measure(unbounded);
  172. width += uie.DesiredSize.Width;
  173. }
  174. // If we're shrinking then remove items to fit
  175. // Also when it first appears the Previous size will be 0 and we need
  176. // to size to what's currently available
  177. if (e.NewSize.Width < e.PreviousSize.Width || e.PreviousSize.Width == 0.0
  178. && e.PreviousSize.Height == 0.0)
  179. {
  180. for (int i = this.PrimaryCommands.Count - 1; i > 0 && width > e.NewSize.Width; i--)
  181. {
  182. var item = this.PrimaryCommands[i];
  183. var element = item as UIElement;
  184. width -= element.DesiredSize.Width;
  185. if (this.overflow.Count == 0 && !(element is AppBarSeparator))
  186. {
  187. // Insert a separator to differentiate between the two sets
  188. this.SecondaryCommands.Insert(0, new AppBarSeparator());
  189. }
  190. overflow.Push(new Tuple<ICommandBarElement, double>(item, element.DesiredSize.Width));
  191. this.PrimaryCommands.RemoveAt(i);
  192. this.SecondaryCommands.Insert(0, (ICommandBarElement)element);
  193. }
  194. }
  195. else if (e.NewSize.Width > e.PreviousSize.Width) // otherwise if we're growing then move items back out
  196. {
  197. while (overflow.Count > 0 && width < e.NewSize.Width)
  198. {
  199. if (width + overflow.Peek().Item2 <= e.NewSize.Width)
  200. {
  201. var t = overflow.Pop();
  202. ((Control)t.Item1).LayoutUpdated += (s, a) =>
  203. {
  204. };
  205. this.SecondaryCommands.Remove(t.Item1);
  206. if (this.overflow.Count == 0 && this.SecondaryCommands[0] is AppBarSeparator)
  207. {
  208. this.SecondaryCommands.RemoveAt(0);
  209. }
  210. // Sometimes this property is being set to disabled
  211. ((Control)t.Item1).IsEnabled = true;
  212. this.PrimaryCommands.Insert(this.PrimaryCommands.Count, t.Item1);
  213. var control = t.Item1 as Control;
  214. width += t.Item2;
  215. }
  216. else
  217. {
  218. break;
  219. }
  220. }
  221. }
  222. }
  223. }
  224. }