Browse Source

Added TabControl.

pull/4/head
Steven Kirk 12 years ago
parent
commit
454393b71a
  1. 18
      Perspex.UnitTests/PerspexObjectTests.cs
  2. 4
      Perspex.UnitTests/Styling/SelectorTests_Class.cs
  3. 4
      Perspex.UnitTests/Styling/SelectorTests_OfType.cs
  4. 37
      Perspex/ControlExtensions.cs
  5. 64
      Perspex/Controls/ContentPresenter.cs
  6. 8
      Perspex/Controls/Control.cs
  7. 22
      Perspex/Controls/Controls.cs
  8. 12
      Perspex/Controls/DataTemplate.cs
  9. 42
      Perspex/Controls/ItemsControl.cs
  10. 18
      Perspex/Controls/ItemsPresenter.cs
  11. 6
      Perspex/Controls/Panel.cs
  12. 20
      Perspex/Controls/SelectingItemsControl.cs
  13. 71
      Perspex/Controls/TabControl.cs
  14. 42
      Perspex/Controls/TabStrip.cs
  15. 5
      Perspex/Perspex.csproj
  16. 17
      Perspex/PerspexObject.cs
  17. 43
      Perspex/PriorityValue.cs
  18. 2
      Perspex/Themes/Default/CheckBoxStyle.cs
  19. 3
      Perspex/Themes/Default/DefaultTheme.cs
  20. 54
      Perspex/Themes/Default/TabControlStyle.cs
  21. 1
      Perspex/Themes/Default/TabItemStyle.cs
  22. 1
      Perspex/Themes/Default/TabStripStyle.cs
  23. 1
      Perspex/VisualExtensions.cs
  24. 104
      TestApplication/Program.cs

18
Perspex.UnitTests/PerspexObjectTests.cs

@ -124,6 +124,24 @@ namespace Perspex.UnitTests
Class1 target = new Class1();
bool raised = false;
target.SetValue(Class1.FooProperty, "bar");
target.PropertyChanged += (s, e) =>
{
raised = true;
};
target.SetValue(Class1.FooProperty, "bar");
Assert.IsFalse(raised);
}
[TestMethod]
public void SetValue_Doesnt_Raise_PropertyChanged_If_Value_Not_Changed_From_Default()
{
Class1 target = new Class1();
bool raised = false;
target.PropertyChanged += (s, e) =>
{
raised = true;

4
Perspex.UnitTests/Styling/SelectorTests_Class.cs

@ -52,7 +52,7 @@ namespace Perspex.UnitTests.Styling
}
[TestMethod]
public void Class_Doesnt_Match_Control_With_TemplatedParent()
public void Class_Matches_Control_With_TemplatedParent()
{
var control = new Control1
{
@ -62,7 +62,7 @@ namespace Perspex.UnitTests.Styling
var target = new Selector().Class("foo");
Assert.IsFalse(ActivatorValue(target, control));
Assert.IsTrue(ActivatorValue(target, control));
}
[TestMethod]

4
Perspex.UnitTests/Styling/SelectorTests_OfType.cs

@ -44,12 +44,12 @@ namespace Perspex.UnitTests.Styling
}
[TestMethod]
public void OfType_Doesnt_Match_Control_With_TemplatedParent()
public void OfType_Matches_Control_With_TemplatedParent()
{
var control = new Control1 { TemplatedParent = new Mock<ITemplatedControl>().Object };
var target = new Selector().OfType<Control1>();
Assert.IsFalse(ActivatorValue(target, control));
Assert.IsTrue(ActivatorValue(target, control));
}
[TestMethod]

37
Perspex/ControlExtensions.cs

@ -0,0 +1,37 @@
// -----------------------------------------------------------------------
// <copyright file="ControlExtensions.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex
{
using System;
using System.Collections.Generic;
using System.Linq;
using Perspex.Controls;
using Perspex.Styling;
public static class ControlExtensions
{
public static IEnumerable<Control> GetTemplateControls(this ITemplatedControl control)
{
return GetTemplateControls(control, (IVisual)control);
}
public static IEnumerable<Control> GetTemplateControls(ITemplatedControl templated, IVisual parent)
{
IVisual visual = parent as IVisual;
foreach (IVisual child in visual.VisualChildren.OfType<Control>().Where(x => x.TemplatedParent == templated))
{
yield return (Control)child;
foreach (IVisual grandchild in GetTemplateControls(templated, child))
{
yield return (Control)grandchild;
}
}
}
}
}

64
Perspex/Controls/ContentPresenter.cs

@ -24,20 +24,20 @@ namespace Perspex.Controls
public ContentPresenter()
{
this.GetObservableWithHistory(ContentProperty).Subscribe(x =>
{
if (x.Item1 is Control)
{
((IVisual)x.Item1).VisualParent = null;
((ILogical)x.Item1).LogicalParent = null;
}
if (x.Item2 is Control)
{
((IVisual)x.Item2).VisualParent = this;
((ILogical)x.Item2).LogicalParent = this;
}
});
this.GetObservableWithHistory(ContentProperty).Subscribe(this.ContentChanged);
//{
// if (x.Item1 is Control)
// {
// ((IVisual)x.Item1).VisualParent = null;
// ((ILogical)x.Item1).LogicalParent = null;
// }
// if (x.Item2 is Control)
// {
// ((IVisual)x.Item2).VisualParent = this;
// ((ILogical)x.Item2).LogicalParent = this;
// }
//});
}
public object Content
@ -160,5 +160,41 @@ namespace Perspex.Controls
return new Size();
}
private void ContentChanged(Tuple<object, object> content)
{
if (content.Item1 != null)
{
this.visualChild.VisualParent = null;
ILogical logical = content.Item1 as ILogical;
if (logical != null)
{
logical.LogicalParent = null;
}
}
if (content.Item2 != null)
{
IVisual visual = content.Item2 as IVisual;
if (visual == null)
{
visual = this.GetDataTemplate(content.Item2).Build(content.Item2);
}
visual.VisualParent = this;
this.visualChild = visual;
ILogical logical = content.Item2 as ILogical;
if (logical != null)
{
logical.LogicalParent = (ILogical)this.TemplatedParent;
}
}
this.InvalidateMeasure();
}
}
}

8
Perspex/Controls/Control.cs

@ -214,18 +214,18 @@ namespace Perspex.Controls
protected virtual DataTemplate FindDataTemplate(object content)
{
IVisual visual = content as IVisual;
Control control = content as Control;
if (visual != null)
if (control != null)
{
return new DataTemplate(x => visual);
return new DataTemplate(x => control);
}
ILogical node = this;
while (node != null)
{
Control control = node as Control;
control = node as Control;
if (control != null)
{

22
Perspex/Controls/Controls.cs

@ -0,0 +1,22 @@
// -----------------------------------------------------------------------
// <copyright file="Controls.cs" company="Steven Kirk">
// Copyright 2013 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
using System.Collections.Generic;
namespace Perspex.Controls
{
public class Controls : PerspexList<Control>
{
public Controls()
{
}
public Controls(IEnumerable<Control> items)
: base(items)
{
}
}
}

12
Perspex/Controls/DataTemplate.cs

@ -14,17 +14,17 @@ namespace Perspex.Controls
public static readonly DataTemplate Default =
new DataTemplate(typeof(object), o => new TextBlock { Text = o.ToString() });
public DataTemplate(Func<object, IVisual> build)
public DataTemplate(Func<object, Control> build)
: this(o => true, build)
{
}
public DataTemplate(Type type, Func<object, IVisual> build)
public DataTemplate(Type type, Func<object, Control> build)
: this(o => type.GetTypeInfo().IsAssignableFrom(o.GetType().GetTypeInfo()), build)
{
}
public DataTemplate(Func<object, bool> match, Func<object, IVisual> build)
public DataTemplate(Func<object, bool> match, Func<object, Control> build)
{
Contract.Requires<ArgumentNullException>(match != null);
Contract.Requires<ArgumentNullException>(build != null);
@ -35,17 +35,17 @@ namespace Perspex.Controls
public Func<object, bool> Match { get; private set; }
public Func<object, IVisual> Build { get; private set; }
public Func<object, Control> Build { get; private set; }
}
public class DataTemplate<T> : DataTemplate
{
public DataTemplate(Func<T, IVisual> build)
public DataTemplate(Func<T, Control> build)
: base(typeof(T), o => build((T)o))
{
}
public DataTemplate(Func<T, bool> match, Func<T, IVisual> build)
public DataTemplate(Func<T, bool> match, Func<T, Control> build)
: base(o => (o is T) ? match((T)o) : false, o => build((T)o))
{
}

42
Perspex/Controls/ItemsControl.cs

@ -6,7 +6,9 @@
namespace Perspex.Controls
{
using System;
using System.Collections;
using System.Collections.Generic;
public class ItemsControl : TemplatedControl
{
@ -22,6 +24,8 @@ namespace Perspex.Controls
public static readonly PerspexProperty<DataTemplate> ItemTemplateProperty =
PerspexProperty.Register<ItemsControl, DataTemplate>("ItemTemplate");
private Dictionary<object, Control> itemControls = new Dictionary<object, Control>();
public IEnumerable Items
{
get { return this.GetValue(ItemsProperty); }
@ -39,5 +43,43 @@ namespace Perspex.Controls
get { return this.GetValue(ItemTemplateProperty); }
set { this.SetValue(ItemTemplateProperty, value); }
}
public Control GetControlForItem(object item)
{
Control result;
this.itemControls.TryGetValue(item, out result);
return result;
}
public IEnumerable<Control> GetAllItemControls()
{
return this.itemControls.Values;
}
internal Control CreateItemControl(object item)
{
Control control = this.CreateItemControlOverride(item);
this.itemControls.Add(item, control);
return control;
}
protected virtual Control CreateItemControlOverride(object item)
{
Control control = item as Control;
DataTemplate template = this.ItemTemplate;
if (control != null)
{
return control;
}
else if (template != null)
{
return template.Build(item);
}
else
{
return this.GetDataTemplate(item).Build(item);
}
}
}
}

18
Perspex/Controls/ItemsPresenter.cs

@ -71,27 +71,27 @@ namespace Perspex.Controls
return finalSize;
}
protected override DataTemplate FindDataTemplate(object content)
private Control CreateItemControl(object item)
{
TabItem tabItem = content as TabItem;
ItemsControl i = this.TemplatedParent as ItemsControl;
if (tabItem != null)
if (i != null)
{
return new DataTemplate(_ => tabItem);
return i.CreateItemControl(item);
}
else
{
return this.ItemTemplate ?? base.FindDataTemplate(content);
return this.GetDataTemplate(item).Build(item) as Control;
}
}
private IEnumerable<Control> CreateItems(IEnumerable items)
private IEnumerable<Control> CreateItemControls(IEnumerable items)
{
if (items != null)
{
return items
.Cast<object>()
.Select(x => this.GetDataTemplate(x).Build(x))
.Select(x => this.CreateItemControl(x))
.OfType<Control>();
}
else
@ -116,7 +116,7 @@ namespace Perspex.Controls
{
if (this.panel != null)
{
this.panel.Children = new PerspexList<Control>(this.CreateItems(items));
this.panel.Children = new Controls(this.CreateItemControls(items));
}
}
}

6
Perspex/Controls/Panel.cs

@ -19,17 +19,17 @@ namespace Perspex.Controls
/// </summary>
public class Panel : Control, IVisual
{
private PerspexList<Control> children;
private Controls children;
private LogicalChildren<Control> logicalChildren;
public PerspexList<Control> Children
public Controls Children
{
get
{
if (this.children == null)
{
this.children = new PerspexList<Control>();
this.children = new Controls();
this.logicalChildren = new LogicalChildren<Control>(this, this.children);
}

20
Perspex/Controls/SelectingItemsControl.cs

@ -0,0 +1,20 @@
// -----------------------------------------------------------------------
// <copyright file="SelectingItemsControl.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
public class SelectingItemsControl : ItemsControl
{
public static readonly PerspexProperty<object> SelectedItemProperty =
PerspexProperty.Register<SelectingItemsControl, object>("SelectedItem");
public object SelectedItem
{
get { return this.GetValue(SelectedItemProperty); }
set { this.SetValue(SelectedItemProperty, value); }
}
}
}

71
Perspex/Controls/TabControl.cs

@ -0,0 +1,71 @@
// -----------------------------------------------------------------------
// <copyright file="TabControl.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using System;
using System.Collections;
using System.Linq;
using System.Reactive.Linq;
public class TabControl : SelectingItemsControl
{
public static readonly PerspexProperty<object> SelectedContentProperty =
PerspexProperty.Register<TabControl, object>("SelectedContent");
private TabStrip tabStrip;
public TabControl()
{
this.GetObservable(ItemsProperty).Subscribe(this.ItemsChanged);
this.GetObservable(SelectedItemProperty).Skip(1).Subscribe(this.SelectedItemChanged);
}
protected override void OnTemplateApplied()
{
this.tabStrip = this.GetTemplateControls()
.OfType<TabStrip>()
.FirstOrDefault();
if (this.tabStrip != null)
{
this.tabStrip.SelectedItem = this.SelectedItem;
this.tabStrip.GetObservable(TabStrip.SelectedItemProperty).Skip(1).Subscribe(x =>
{
this.SelectedItem = x;
});
}
}
private void ItemsChanged(IEnumerable items)
{
if (items != null)
{
this.SelectedItem = items.OfType<object>().FirstOrDefault();
}
else
{
this.SelectedItem = null;
}
}
private void SelectedItemChanged(object item)
{
this.SelectedItem = item;
ContentControl content = item as ContentControl;
if (content != null)
{
this.SetValue(SelectedContentProperty, content.Content);
}
else
{
this.SetValue(SelectedContentProperty, item);
}
}
}
}

42
Perspex/Controls/TabStrip.cs

@ -4,11 +4,12 @@
// </copyright>
// -----------------------------------------------------------------------
using Perspex.Input;
namespace Perspex.Controls
{
public class TabStrip : ItemsControl
using System;
using Perspex.Input;
public class TabStrip : SelectingItemsControl
{
private static readonly ItemsPanelTemplate PanelTemplate = new ItemsPanelTemplate(
() => new StackPanel());
@ -25,6 +26,24 @@ namespace Perspex.Controls
public TabStrip()
{
this.PointerPressed += this.OnPointerPressed;
this.GetObservable(SelectedItemProperty).Subscribe(this.SelectedItemChanged);
}
protected override Control CreateItemControlOverride(object item)
{
TabItem result = item as TabItem;
if (result == null)
{
result = new TabItem
{
Content = item,
};
}
result.IsSelected = this.SelectedItem == item;
return result;
}
private void OnPointerPressed(object sender, PointerEventArgs e)
@ -36,16 +55,19 @@ namespace Perspex.Controls
{
TabItem item = presenter.TemplatedParent as TabItem;
if (item != null && item.TemplatedParent == this)
if (item != null)
{
item.IsSelected = true;
foreach (var i in item.GetVisualSiblings<TabItem>())
{
i.IsSelected = false;
}
this.SelectedItem = item;
}
}
}
private void SelectedItemChanged(object selectedItem)
{
foreach (TabItem item in this.GetAllItemControls())
{
item.IsSelected = item == selectedItem;
}
}
}
}

5
Perspex/Perspex.csproj

@ -71,7 +71,10 @@
<Compile Include="Application.cs" />
<Compile Include="BindingExtensions.cs" />
<Compile Include="Controls\HeaderedContentControl.cs" />
<Compile Include="Controls\Controls.cs" />
<Compile Include="Controls\SelectingItemsControl.cs" />
<Compile Include="Controls\TabItem.cs" />
<Compile Include="Controls\TabControl.cs" />
<Compile Include="Controls\TabStrip.cs" />
<Compile Include="Controls\ItemsPresenter.cs" />
<Compile Include="Controls\ItemsPanelTemplate.cs" />
@ -134,6 +137,7 @@
<Compile Include="Themes\Default\ContentControlStyle.cs" />
<Compile Include="Themes\Default\ItemsControlStyle.cs" />
<Compile Include="Themes\Default\TabItemStyle.cs" />
<Compile Include="Themes\Default\TabControlStyle.cs" />
<Compile Include="Themes\Default\TabStripStyle.cs" />
<Compile Include="Vector.cs" />
<Compile Include="Rendering\RenderManager.cs" />
@ -205,6 +209,7 @@
<Compile Include="Threading\DispatcherTimer.cs" />
<Compile Include="Visual.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ControlExtensions.cs" />
<Compile Include="VisualExtensions.cs" />
</ItemGroup>
<ItemGroup>

17
Perspex/PerspexObject.cs

@ -412,8 +412,7 @@ namespace Perspex
this.GetHashCode(),
value));
v.Clear(Priority);
v.Add(Observable.Never<object>().StartWith(value), Priority);
v.Replace(Observable.Never<object>().StartWith(value), Priority);
}
/// <summary>
@ -455,11 +454,6 @@ namespace Perspex
this.values.Add(property, v);
}
if (priority == BindingPriority.LocalValue)
{
v.Clear((int)priority);
}
this.Log().Debug(string.Format(
"Bound value of {0}.{1} (#{2:x8}) to {3}",
this.GetType().Name,
@ -467,7 +461,14 @@ namespace Perspex
this.GetHashCode(),
description != null ? description.Description : "[Anonymous]"));
return v.Add(source, (int)priority);
if (priority == BindingPriority.LocalValue)
{
return v.Replace(source, (int)priority);
}
else
{
return v.Add(source, (int)priority);
}
}
/// <summary>

43
Perspex/PriorityValue.cs

@ -109,6 +109,49 @@ namespace Perspex
});
}
/// <summary>
/// Adds a new binding, replacing all those of the same priority.
/// </summary>
/// <param name="binding">The binding.</param>
/// <param name="priority">The binding priority.</param>
/// <returns>
/// A disposable that will remove the binding.
/// </returns>
public IDisposable Replace(IObservable<object> binding, int priority)
{
BindingEntry entry = new BindingEntry();
LinkedListNode<BindingEntry> insert = this.bindings.First;
while (insert != null && insert.Value.Priority < priority)
{
insert = insert.Next;
}
while (insert != null && insert.Value.Priority == priority)
{
LinkedListNode<BindingEntry> next = insert.Next;
insert.Value.Dispose();
this.bindings.Remove(insert);
insert = next;
}
if (insert == null)
{
this.bindings.AddLast(entry);
}
else
{
this.bindings.AddBefore(insert, entry);
}
entry.Start(binding, priority, this.EntryChanged, this.EntryCompleted);
return Disposable.Create(() =>
{
this.Remove(entry);
});
}
/// <summary>
/// Removes all bindings with the specified priority.
/// </summary>

2
Perspex/Themes/Default/CheckBoxStyle.cs

@ -56,7 +56,7 @@ namespace Perspex.Themes.Default
new ColumnDefinition(GridLength.Auto),
new ColumnDefinition(new GridLength(1, GridUnitType.Star)),
},
Children = new PerspexList<Control>
Children = new Controls
{
new Border
{

3
Perspex/Themes/Default/DefaultTheme.cs

@ -16,9 +16,10 @@ namespace Perspex.Themes.Default
this.Add(new CheckBoxStyle());
this.Add(new ContentControlStyle());
this.Add(new ItemsControlStyle());
this.Add(new TextBoxStyle());
this.Add(new TabControlStyle());
this.Add(new TabItemStyle());
this.Add(new TabStripStyle());
this.Add(new TextBoxStyle());
}
}
}

54
Perspex/Themes/Default/TabControlStyle.cs

@ -0,0 +1,54 @@
// -----------------------------------------------------------------------
// <copyright file="TabControlStyle.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Themes.Default
{
using System.Linq;
using System.Reactive.Linq;
using Perspex.Controls;
using Perspex.Styling;
public class TabControlStyle : Styles
{
public TabControlStyle()
{
this.AddRange(new[]
{
new Style(x => x.OfType<TabControl>())
{
Setters = new[]
{
new Setter(TabControl.TemplateProperty, ControlTemplate.Create<TabControl>(this.Template)),
},
},
});
}
private Control Template(TabControl control)
{
return new Grid
{
RowDefinitions = new RowDefinitions
{
new RowDefinition(GridLength.Auto),
new RowDefinition(new GridLength(1, GridUnitType.Star)),
},
Children = new Controls
{
new TabStrip
{
[~TabStrip.ItemsProperty] = control[~TabControl.ItemsProperty],
},
new ContentPresenter
{
[~ContentPresenter.ContentProperty] = control[~TabControl.SelectedContentProperty],
[Grid.RowProperty] = 1,
}
}
};
}
}
}

1
Perspex/Themes/Default/TabItemStyle.cs

@ -23,7 +23,6 @@ namespace Perspex.Themes.Default
{
new Setter(TextBox.FontSizeProperty, 28.7),
new Setter(Control.ForegroundProperty, Brushes.Gray),
new Setter(Control.MarginProperty, new Thickness(8, 0)),
new Setter(TabItem.TemplateProperty, ControlTemplate.Create<TabItem>(this.Template)),
},
},

1
Perspex/Themes/Default/TabStripStyle.cs

@ -27,6 +27,7 @@ namespace Perspex.Themes.Default
{
Setters = new[]
{
new Setter(StackPanel.GapProperty, 16.0),
new Setter(StackPanel.OrientationProperty, Orientation.Horizontal),
},
},

1
Perspex/VisualExtensions.cs

@ -9,6 +9,7 @@ namespace Perspex
using System;
using System.Collections.Generic;
using System.Linq;
using Perspex.Controls;
using Perspex.Styling;
public static class VisualExtensions

104
TestApplication/Program.cs

@ -36,55 +36,85 @@ namespace TestApplication
{
App application = new App();
Locator.CurrentMutable.Register(() => new TestLogger { Level = LogLevel.Debug } , typeof(ILogger));
//Locator.CurrentMutable.Register(() => new TestLogger { Level = LogLevel.Debug } , typeof(ILogger));
Window window = new Window
{
Content = new StackPanel
Content = new TabControl
{
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Orientation = Orientation.Vertical,
Gap = 6,
Children = new PerspexList<Control>
Items = new[]
{
new Button
new TabItem
{
Content = "Button",
},
new Button
{
Content = "Explict Background",
Background = new SolidColorBrush(0xffa0a0ff),
},
new CheckBox
{
Content = "Checkbox",
},
new TextBox
{
Text = "Hello World!",
Header = "Buttons",
Content = new StackPanel
{
Orientation = Orientation.Vertical,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Gap = 8,
MinWidth = 120,
Children = new Controls
{
new Button
{
Content = "Button",
},
new Button
{
Content = "Button",
Background = new SolidColorBrush(0xcc119eda),
},
new CheckBox
{
Content = "Checkbox",
},
}
},
},
new Image
new TabItem
{
Source = new Bitmap("github_icon.png"),
Width = 200,
Header = "Text",
Content = new StackPanel
{
Orientation = Orientation.Vertical,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Gap = 8,
Width = 120,
Children = new Controls
{
new TextBlock
{
Text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin venenatis dui quis libero suscipit tincidunt.",
},
new TextBox
{
Text = "Text Box",
},
}
},
},
new TabStrip
new TabItem
{
Items = new[]
Header = "Images",
Content = new StackPanel
{
new TabItem
{
Header = "Tab 1",
IsSelected = true,
},
new TabItem
Orientation = Orientation.Vertical,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Gap = 8,
Width = 120,
Children = new Controls
{
Header = "Tab 2",
},
}
}
new Image
{
Source = new Bitmap("github_icon.png"),
Width = 200,
},
}
},
},
}
}
};

Loading…
Cancel
Save