diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Project/Controls/AdornerLayer.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Project/Controls/AdornerLayer.cs new file mode 100644 index 0000000000..7f1f612021 --- /dev/null +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Project/Controls/AdornerLayer.cs @@ -0,0 +1,191 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Diagnostics; +using System.Collections.Generic; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using ICSharpCode.WpfDesign.Adorners; + +namespace ICSharpCode.WpfDesign.Designer.Controls +{ + /// + /// A control that displays adorner panels. + /// + sealed class AdornerLayer : Panel + { + #region AdornerPanelCollection + internal sealed class AdornerPanelCollection : ICollection + { + readonly AdornerLayer _layer; + + public AdornerPanelCollection(AdornerLayer layer) + { + this._layer = layer; + } + + public int Count { + get { return _layer.Children.Count; } + } + + public bool IsReadOnly { + get { return false; } + } + + public void Add(AdornerPanel item) + { + if (item == null) + throw new ArgumentNullException("item"); + + _layer.AddAdorner(item); + } + + public void Clear() + { + _layer.ClearAdorners(); + } + + public bool Contains(AdornerPanel item) + { + if (item == null) + throw new ArgumentNullException("item"); + + return VisualTreeHelper.GetParent(item) == _layer; + } + + public void CopyTo(AdornerPanel[] array, int arrayIndex) + { + Linq.ToArray(this).CopyTo(array, arrayIndex); + } + + public bool Remove(AdornerPanel item) + { + if (item == null) + throw new ArgumentNullException("item"); + + return _layer.RemoveAdorner(item); + } + + public IEnumerator GetEnumerator() + { + foreach (AdornerPanel panel in _layer.Children) { + yield return panel; + } + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + } + #endregion + + AdornerPanelCollection _adorners; + readonly UIElement _designPanel; + + internal AdornerLayer(UIElement designPanel) + { + this._designPanel = designPanel; + + _adorners = new AdornerPanelCollection(this); + ClearAdorners(); + } + + internal AdornerPanelCollection Adorners { + get { + return _adorners; + } + } + + sealed class AdornerInfo + { + internal readonly List adorners = new List(); + } + + // adorned element => AdornerInfo + Dictionary _dict; + + void ClearAdorners() + { + this.Children.Clear(); + _dict = new Dictionary(); + } + + AdornerInfo GetAdornerInfo(UIElement adornedElement) + { + AdornerInfo info; + if (!_dict.TryGetValue(adornedElement, out info)) { + info = _dict[adornedElement] = new AdornerInfo(); + } + return info; + } + + AdornerInfo GetExistingAdornerInfo(UIElement adornedElement) + { + AdornerInfo info; + _dict.TryGetValue(adornedElement, out info); + return info; + } + + void AddAdorner(AdornerPanel adornerPanel) + { + if (adornerPanel.AdornedElement == null) + throw new DesignerException("adornerPanel.AdornedElement must be set"); + + GetAdornerInfo(adornerPanel.AdornedElement).adorners.Add(adornerPanel); + + UIElementCollection children = this.Children; + int i = 0; + for (i = 0; i < children.Count; i++) { + AdornerPanel p = (AdornerPanel)children[i]; + if (p.Order.CompareTo(adornerPanel.Order) > 0) { + break; + } + } + children.Insert(i, adornerPanel); + + this.InvalidateMeasure(); + } + + protected override Size MeasureOverride(Size availableSize) + { + Size infiniteSize = new Size(double.PositiveInfinity, double.PositiveInfinity); + foreach (AdornerPanel adorner in this.Children) { + adorner.Measure(infiniteSize); + } + return new Size(0, 0); + } + + protected override Size ArrangeOverride(Size finalSize) + { + foreach (AdornerPanel adorner in this.Children) { + adorner.Arrange(new Rect(new Point(0, 0), adorner.DesiredSize)); + adorner.RenderTransform = (Transform)adorner.AdornedElement.TransformToAncestor(_designPanel); + } + return finalSize; + } + + bool RemoveAdorner(AdornerPanel adornerPanel) + { + if (adornerPanel.AdornedElement == null) + return false; + + AdornerInfo info = GetExistingAdornerInfo(adornerPanel.AdornedElement); + if (info == null) + return false; + + if (info.adorners.Remove(adornerPanel)) { + this.Children.Remove(adornerPanel); + return true; + } else { + return false; + } + } + } +} diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Project/Controls/SingleVisualChildElement.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Project/Controls/SingleVisualChildElement.cs index bcfdabd7af..efc1a5497e 100644 --- a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Project/Controls/SingleVisualChildElement.cs +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Project/Controls/SingleVisualChildElement.cs @@ -33,7 +33,7 @@ namespace ICSharpCode.WpfDesign.Designer.Controls } /// - /// Gets the visual child the design surfaces uses to display itself. + /// Gets the visual child. /// protected override Visual GetVisualChild(int index) { @@ -44,7 +44,7 @@ namespace ICSharpCode.WpfDesign.Designer.Controls } /// - /// Gets the number of visual children the design surface has. + /// Gets the number of visual children. /// protected override int VisualChildrenCount { get { return _visualChild != null ? 1 : 0; } diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Project/DesignPanel.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Project/DesignPanel.cs index 61f186020a..b8f95e81d2 100644 --- a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Project/DesignPanel.cs +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Project/DesignPanel.cs @@ -11,42 +11,106 @@ using System.Windows; using System.Windows.Input; using System.Windows.Controls; using System.Windows.Media; +using System.Collections.Generic; using System.Windows.Threading; using ICSharpCode.WpfDesign.Designer.Controls; +using ICSharpCode.WpfDesign.Adorners; namespace ICSharpCode.WpfDesign.Designer { - sealed class DesignPanel : SingleVisualChildElement, IDesignPanel + sealed class DesignPanel : Decorator, IDesignPanel { - sealed class InnerDesignPanel : SingleVisualChildElement + /// + /// this element is always hit (unless HitTestVisible is set to false) + /// + sealed class EatAllHitTestRequests : UIElement { - internal void SetElement(UIElement element) + protected override GeometryHitTestResult HitTestCore(GeometryHitTestParameters hitTestParameters) + { + return new GeometryHitTestResult(this, IntersectionDetail.FullyContains); + } + + protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters) { - this.VisualChild = element; + return new PointHitTestResult(this, hitTestParameters.HitPoint); } } DesignContext _context; - InnerDesignPanel _innerDesignPanel; - UIElement _designedElement; + EatAllHitTestRequests _eatAllHitTestRequests; + AdornerLayer _adornerLayer; public DesignPanel() { this.Focusable = true; - - _innerDesignPanel = new InnerDesignPanel(); - this.VisualChild = _innerDesignPanel; + _eatAllHitTestRequests = new EatAllHitTestRequests(); + _eatAllHitTestRequests.IsHitTestVisible = false; + _adornerLayer = new AdornerLayer(this); } - public UIElement DesignedElement { + #region Visual Child Management + public override UIElement Child { + get { return base.Child; } + set { + if (base.Child == value) + return; + if (value == null) { + // Child is being set from some value to null + + // remove _adornerLayer and _eatAllHitTestRequests + RemoveVisualChild(_adornerLayer); + RemoveVisualChild(_eatAllHitTestRequests); + } else if (base.Child == null) { + // Child is being set from null to some value + AddVisualChild(_adornerLayer); + AddVisualChild(_eatAllHitTestRequests); + } + base.Child = value; + } + } + + protected override Visual GetVisualChild(int index) + { + if (base.Child != null) { + if (index == 0) + return base.Child; + else if (index == 1) + return _eatAllHitTestRequests; + else if (index == 2) + return _adornerLayer; + } + return base.GetVisualChild(index); + } + + protected override int VisualChildrenCount { get { - return _designedElement; + if (base.Child != null) + return 3; + else + return base.VisualChildrenCount; } - set { - _designedElement = value; - _innerDesignPanel.SetElement(value); + } + + protected override Size MeasureOverride(Size constraint) + { + Size result = base.MeasureOverride(constraint); + if (this.Child != null) { + _adornerLayer.Measure(constraint); + _eatAllHitTestRequests.Measure(constraint); + } + return result; + } + + protected override Size ArrangeOverride(Size arrangeSize) + { + Size result = base.ArrangeOverride(arrangeSize); + if (this.Child != null) { + _adornerLayer.Arrange(new Rect(new Point(0, 0), arrangeSize)); + _eatAllHitTestRequests.Arrange(new Rect(new Point(0, 0), arrangeSize)); } + return result; } + #endregion /// /// Gets/Sets the design context. @@ -61,16 +125,6 @@ namespace ICSharpCode.WpfDesign.Designer get { return _context.Services.Tool; } } - protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters) - { - return new PointHitTestResult(this, hitTestParameters.HitPoint); - } - - protected override GeometryHitTestResult HitTestCore(GeometryHitTestParameters hitTestParameters) - { - return new GeometryHitTestResult(this, IntersectionDetail.NotCalculated); - } - protected override void OnPreviewMouseDown(MouseButtonEventArgs e) { base.OnPreviewMouseDown(e); @@ -98,7 +152,7 @@ namespace ICSharpCode.WpfDesign.Designer DesignItem site = _context.Services.Component.GetDesignItem(originalSource); if (site != null) return site; - if (originalSource == _innerDesignPanel) + if (originalSource == this) return null; DependencyObject dObj = originalSource as DependencyObject; if (dObj == null) @@ -128,14 +182,20 @@ namespace ICSharpCode.WpfDesign.Designer { if (_isInInputAction) throw new InvalidOperationException(); _isInInputAction = true; - _innerDesignPanel.IsHitTestVisible = false; + _eatAllHitTestRequests.IsHitTestVisible = true; } void IDesignPanel.StopInputAction() { if (!_isInInputAction) throw new InvalidOperationException(); _isInInputAction = false; - _innerDesignPanel.IsHitTestVisible = true; + _eatAllHitTestRequests.IsHitTestVisible = false; + } + + public ICollection Adorners { + get { + return _adornerLayer.Adorners; + } } } diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Project/DesignSurface.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Project/DesignSurface.cs index 45a8c1cf2d..a76ddc2fdb 100644 --- a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Project/DesignSurface.cs +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Project/DesignSurface.cs @@ -48,7 +48,7 @@ namespace ICSharpCode.WpfDesign.Designer /// public UIElement DesignedElement { get { - return _designPanel.DesignedElement; + return _designPanel.Child; } } @@ -71,9 +71,11 @@ namespace ICSharpCode.WpfDesign.Designer void InitializeDesigner(DesignContext context) { + context.Services.AddService(typeof(IDesignPanel), _designPanel); + _designContext = context; _designPanel.Context = context; - _designPanel.DesignedElement = context.RootItem.View; + _designPanel.Child = context.RootItem.View; } /// @@ -83,7 +85,7 @@ namespace ICSharpCode.WpfDesign.Designer { _designContext = null; _designPanel.Context = null; - _designPanel.DesignedElement = null; + _designPanel.Child = null; } } } diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Project/Extensions/SelectedElementRectangleExtension.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Project/Extensions/SelectedElementRectangleExtension.cs new file mode 100644 index 0000000000..b86a0ec701 --- /dev/null +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Project/Extensions/SelectedElementRectangleExtension.cs @@ -0,0 +1,43 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Windows.Media; +using System.Windows.Shapes; +using ICSharpCode.WpfDesign.Adorners; +using ICSharpCode.WpfDesign.Extensions; +using ICSharpCode.WpfDesign.Designer.Controls; +using System.Windows; + +namespace ICSharpCode.WpfDesign.Designer.Extensions +{ + /// + /// Draws a dotted line around selected UIElements. + /// + [ExtensionFor(typeof(UIElement))] + public class SelectedElementRectangleExtension : SelectionAdornerProvider + { + /// + /// Creates a new SelectedElementRectangleExtension instance. + /// + public SelectedElementRectangleExtension() + { + Rectangle r = new Rectangle(); + r.SnapsToDevicePixels = true; + r.Stroke = Brushes.Black; + r.StrokeDashCap = PenLineCap.Square; + r.StrokeDashArray = new DoubleCollection(new double[] { 0, 2 }); + r.IsHitTestVisible = false; + + Placement placement = new Placement(); + placement.WidthRelativeToContentWidth = 1; + placement.HeightRelativeToContentHeight = 1; + + this.AddAdorner(r, placement); + } + } +} diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Project/Services/SelectionService.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Project/Services/SelectionService.cs index 17e09c9624..46de417382 100644 --- a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Project/Services/SelectionService.cs +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Project/Services/SelectionService.cs @@ -46,7 +46,7 @@ namespace ICSharpCode.WpfDesign.Designer.Services public void SetSelectedComponents(ICollection components) { - SetSelectedComponents(components, SelectionTypes.Auto); + SetSelectedComponents(components, SelectionTypes.Replace); } public void SetSelectedComponents(ICollection components, SelectionTypes selectionType) diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Project/Services/ToolService.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Project/Services/ToolService.cs index 93ad29feb7..fa4d87d6cb 100644 --- a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Project/Services/ToolService.cs +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Project/Services/ToolService.cs @@ -10,9 +10,7 @@ using System.Windows.Input; namespace ICSharpCode.WpfDesign.Designer.Services { - /// - /// See for description. - /// + // See IToolService for description. sealed class DefaultToolService : IToolService { PointerTool _pointerTool; @@ -57,11 +55,13 @@ namespace ICSharpCode.WpfDesign.Designer.Services abstract class MouseGestureBase { protected IDesignPanel designPanel; + protected ServiceContainer services; bool isStarted; public void Start(IDesignPanel designPanel, MouseButtonEventArgs e) { this.designPanel = designPanel; + this.services = designPanel.Context.Services; isStarted = true; designPanel.StartInputAction(); RegisterEvents(); @@ -125,6 +125,12 @@ namespace ICSharpCode.WpfDesign.Designer.Services protected override void OnStarted(MouseButtonEventArgs e) { base.OnStarted(e); + DesignItem item = designPanel.FindDesignedElementForOriginalSource(e.OriginalSource); + if (item != null) { + services.Selection.SetSelectedComponents(new DesignItem[] { item }, SelectionTypes.Auto); + } else { + services.Selection.SetSelectedComponents(new DesignItem[] { }, SelectionTypes.Auto); + } } protected override void OnStopped() diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Project/WpfDesign.Designer.csproj b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Project/WpfDesign.Designer.csproj index 840b6ae382..220c6c03b5 100644 --- a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Project/WpfDesign.Designer.csproj +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Project/WpfDesign.Designer.csproj @@ -56,8 +56,10 @@ Configuration\GlobalAssemblyInfo.cs + + diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Adorners/AdornerPanel.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Adorners/AdornerPanel.cs new file mode 100644 index 0000000000..cbd4e4beed --- /dev/null +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Adorners/AdornerPanel.cs @@ -0,0 +1,117 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; + +namespace ICSharpCode.WpfDesign.Adorners +{ + /// + /// Manages display of adorners on the design surface. + /// + public sealed class AdornerPanel : Panel + { + #region Attached Property Placement + /// + /// The dependency property used to store the placement of adorner visuals. + /// + public static readonly DependencyProperty PlacementProperty = DependencyProperty.RegisterAttached( + "Placement", typeof(Placement), typeof(AdornerPanel), + new FrameworkPropertyMetadata(new Placement(), FrameworkPropertyMetadataOptions.AffectsParentMeasure) + ); + + /// + /// Gets the placement of the specified adorner visual. + /// + public static Placement GetPlacement(Visual visual) + { + if (visual == null) + throw new ArgumentNullException("visual"); + return (Placement)visual.GetValue(PlacementProperty); + } + + /// + /// Sets the placement of the specified adorner visual. + /// + public static void SetPlacement(Visual visual, Placement placement) + { + if (visual == null) + throw new ArgumentNullException("visual"); + if (placement == null) + throw new ArgumentNullException("placement"); + visual.SetValue(PlacementProperty, placement); + } + #endregion + + UIElement _adornedElement; + AdornerOrder _Order = AdornerOrder.Content; + + public UIElement AdornedElement { + get { return _adornedElement; } + set { _adornedElement = value; } + } + + public AdornerOrder Order { + get { return _Order; } + set { _Order = value; } + } + + protected override Size MeasureOverride(Size availableSize) + { + if (this.AdornedElement != null) { + foreach (DependencyObject v in this.VisualChildren) { + UIElement e = v as UIElement; + if (e != null) { + e.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); + } + } + return this.AdornedElement.RenderSize; + } else { + return base.MeasureOverride(availableSize); + } + } + + protected override Size ArrangeOverride(Size finalSize) + { + foreach (UIElement element in base.InternalChildren) { + element.Arrange(new Rect(finalSize)); + } + return finalSize; + } + + private IEnumerable VisualChildren { + get { + int count = VisualTreeHelper.GetChildrenCount(this); + for (int i = 0; i < count; i++) { + yield return VisualTreeHelper.GetChild(this, i); + } + } + } + } + + public struct AdornerOrder : IComparable + { + public static readonly AdornerOrder Background = new AdornerOrder(100); + public static readonly AdornerOrder Content = new AdornerOrder(200); + public static readonly AdornerOrder Foreground = new AdornerOrder(300); + + int i; + + public AdornerOrder(int i) + { + this.i = i; + } + + public int CompareTo(AdornerOrder other) + { + return i.CompareTo(other.i); + } + } +} diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Adorners/AdornerProvider.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Adorners/AdornerProvider.cs new file mode 100644 index 0000000000..43e0aa00e6 --- /dev/null +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Adorners/AdornerProvider.cs @@ -0,0 +1,145 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.ObjectModel; +using System.Windows; +using System.Windows.Media; +using ICSharpCode.WpfDesign.Extensions; + +namespace ICSharpCode.WpfDesign.Adorners +{ + /// + /// Base class for extensions that present adorners on the screen. + /// + /// + /// About design-time adorners and their placement: + /// read http://myfun.spaces.live.com/blog/cns!AC1291870308F748!240.entry + /// and http://myfun.spaces.live.com/blog/cns!AC1291870308F748!242.entry + /// + public abstract class AdornerProvider : DefaultExtension + { + #region class AdornerCollection + /// + /// Describes a collection of adorner visuals. + /// + sealed class AdornerPanelCollection : Collection + { + readonly AdornerProvider _provider; + + internal AdornerPanelCollection(AdornerProvider provider) + { + this._provider = provider; + } + + /// + protected override void InsertItem(int index, AdornerPanel item) + { + base.InsertItem(index, item); + _provider.OnAdornerAdd(item); + } + + /// + protected override void RemoveItem(int index) + { + _provider.OnAdornerRemove(base[index]); + base.RemoveItem(index); + } + + /// + protected override void SetItem(int index, AdornerPanel item) + { + _provider.OnAdornerRemove(base[index]); + base.SetItem(index, item); + _provider.OnAdornerAdd(item); + } + + /// + protected override void ClearItems() + { + foreach (AdornerPanel v in this) { + _provider.OnAdornerRemove(v); + } + base.ClearItems(); + } + } + #endregion + + AdornerPanelCollection _adorners; + bool isVisible; + + /// + /// Creates a new AdornerProvider instance. + /// + public AdornerProvider() + { + _adorners = new AdornerPanelCollection(this); + } + + /// + /// Is called after the ExtendedItem was set. + /// This methods displays the registered adorners + /// + protected override void OnInitialized() + { + base.OnInitialized(); + isVisible = true; + foreach (AdornerPanel v in _adorners) { + OnAdornerAdd(v); + } + } + + /// + /// Is called when the extension is removed. + /// This method hides the registered adorners. + /// + protected override void OnRemove() + { + base.OnRemove(); + foreach (AdornerPanel v in _adorners) { + OnAdornerRemove(v); + } + isVisible = false; + } + + /// + /// Gets the list of adorners displayed by this AdornerProvider. + /// + public Collection Adorners { + get { return _adorners; } + } + + /// + /// Adds an UIElement as adorner with the specified placement. + /// + protected void AddAdorner(UIElement adorner, Placement placement) + { + AdornerPanel.SetPlacement(adorner, placement); + AdornerPanel p = new AdornerPanel(); + p.Children.Add(adorner); + this.Adorners.Add(p); + } + + internal void OnAdornerAdd(AdornerPanel item) + { + if (!isVisible) return; + + item.AdornedElement = this.ExtendedItem.View; + + IDesignPanel avs = Services.GetService(); + avs.Adorners.Add(item); + } + + internal void OnAdornerRemove(AdornerPanel item) + { + if (!isVisible) return; + + IDesignPanel avs = Services.GetService(); + avs.Adorners.Remove(item); + } + } +} diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Adorners/AdornerProviderClasses.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Adorners/AdornerProviderClasses.cs new file mode 100644 index 0000000000..a55146a917 --- /dev/null +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Adorners/AdornerProviderClasses.cs @@ -0,0 +1,50 @@ +// +// +// +// +// $Revision$ +// + +using System; +using ICSharpCode.WpfDesign.Extensions; + +namespace ICSharpCode.WpfDesign.Adorners +{ + // Some classes that derive from AdornerProvider to specify a certain ExtensionServer. + + /// + /// An adorner extension that is attached permanently. + /// + [ExtensionServer(typeof(DefaultExtensionServer.Permanent))] + public abstract class PermanentAdornerProvider : AdornerProvider + { + + } + + /// + /// An adorner extension that is attached to selected components. + /// + [ExtensionServer(typeof(SelectionExtensionServer))] + public abstract class SelectionAdornerProvider : AdornerProvider + { + + } + + /// + /// An adorner extension that is attached to the primary selection. + /// + [ExtensionServer(typeof(PrimarySelectionExtensionServer))] + public abstract class PrimarySelectionAdornerProvider : AdornerProvider + { + + } + + /// + /// An adorner extension that is attached to the secondary selection. + /// + [ExtensionServer(typeof(SecondarySelectionExtensionServer))] + public abstract class SecondarySelectionAdornerProvider : AdornerProvider + { + + } +} diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Adorners/Placement.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Adorners/Placement.cs new file mode 100644 index 0000000000..c787fc5f37 --- /dev/null +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Adorners/Placement.cs @@ -0,0 +1,150 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.ObjectModel; +using System.Windows; +using System.Windows.Media; + +namespace ICSharpCode.WpfDesign.Adorners +{ + // We have to support the different coordinate spaces as explained in + // http://myfun.spaces.live.com/blog/cns!AC1291870308F748!242.entry + + /// + /// Defines how a design-time adorner is placed. + /// + public class Placement + { + PlacementSpace space = PlacementSpace.Render; + + /// + /// Gets/Sets the space in which the adorner is placed. + /// + public PlacementSpace Space { + get { return space; } + set { space = value; } + } + + double widthRelativeToDesiredWidth, heightRelativeToDesiredHeight; + + /// + /// Gets/Sets the width of the adorner relative to the desired adorner width. + /// + public double WidthRelativeToDesiredWidth { + get { return widthRelativeToDesiredWidth; } + set { widthRelativeToDesiredWidth = value; } + } + + /// + /// Gets/Sets the height of the adorner relative to the desired adorner height. + /// + public double HeightRelativeToDesiredHeight { + get { return heightRelativeToDesiredHeight; } + set { heightRelativeToDesiredHeight = value; } + } + + double widthRelativeToContentWidth, heightRelativeToContentHeight; + + /// + /// Gets/Sets the width of the adorner relative to the width of the adorned item. + /// + public double WidthRelativeToContentWidth { + get { return widthRelativeToContentWidth; } + set { widthRelativeToContentWidth = value; } + } + + /// + /// Gets/Sets the height of the adorner relative to the height of the adorned item. + /// + public double HeightRelativeToContentHeight { + get { return heightRelativeToContentHeight; } + set { heightRelativeToContentHeight = value; } + } + + double widthOffset, heightOffset; + + /// + /// Gets/Sets an offset that is added to the adorner width for the size calculation. + /// + public double WidthOffset { + get { return widthOffset; } + set { widthOffset = value; } + } + + /// + /// Gets/Sets an offset that is added to the adorner height for the size calculation. + /// + public double HeightOffset { + get { return heightOffset; } + set { heightOffset = value; } + } + + Size CalculateSize(Visual adornerVisual, UIElement adornedElement) + { + Size size = new Size(widthOffset, heightOffset); + if (widthRelativeToDesiredWidth != 0 || heightRelativeToDesiredHeight != 0) { + UIElement adornerElement = adornerVisual as UIElement; + if (adornerElement == null) { + throw new DesignerException("Cannot calculate the size relative to the adorner's desired size if the adorner is not an UIElement."); + } + size.Width += widthRelativeToDesiredWidth * adornerElement.DesiredSize.Width; + size.Height += heightRelativeToDesiredHeight * adornerElement.DesiredSize.Height; + } + size.Width += widthRelativeToContentWidth * adornedElement.RenderSize.Width; + size.Height += heightRelativeToContentHeight * adornedElement.RenderSize.Height; + return size; + } + } + + /// + /// Describes the space in which an adorner is placed. + /// + public enum PlacementSpace + { + /// + /// The adorner is affected by the render transform of the adorned element. + /// + Render, + /// + /// The adorner is affected by the layout transform of the adorned element. + /// + Layout, + /// + /// The adorner is not affected by transforms of designed controls. + /// + Designer + } + + /// + /// The possible layers where adorners can be placed. + /// + public enum AdornerZLayer + { + /// + /// This layer is below the other adorner layers. + /// + Low, + /// + /// This layer is for normal background adorners. + /// + Normal, + /// + /// This layer is for selection adorners + /// + Selection, + /// + /// This layer is for primary selection adorners + /// + PrimarySelection, + /// + /// This layer is above the other layers. + /// It is used for temporary drawings, e.g. the selection frame while selecting multiple controls with the mouse. + /// + High + } +} diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/DesignItem.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/DesignItem.cs index ff9e19a9fd..504a3205d4 100644 --- a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/DesignItem.cs +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/DesignItem.cs @@ -71,42 +71,56 @@ namespace ICSharpCode.WpfDesign } } - internal void SetExtensionServers(ExtensionServer[] extensionServers) + internal void SetExtensionServers(ExtensionManager extensionManager, ExtensionServer[] extensionServers) { Debug.Assert(_extensionServers == null); Debug.Assert(extensionServers != null); _extensionServers = extensionServers; _extensionServerIsApplied = new bool[extensionServers.Length]; + + for (int i = 0; i < _extensionServers.Length; i++) { + bool shouldApply = _extensionServers[i].ShouldApplyExtensions(this); + if (shouldApply != _extensionServerIsApplied[i]) { + _extensionServerIsApplied[i] = shouldApply; + ApplyUnapplyExtensionServer(extensionManager, shouldApply, _extensionServers[i]); + } + } } - internal void ApplyExtensions(ExtensionManager extensionManager) + internal void ReapplyExtensionServer(ExtensionManager extensionManager, ExtensionServer server) { - Debug.Assert(_extensionServers != null); for (int i = 0; i < _extensionServers.Length; i++) { - bool shouldApply = _extensionServers[i].ShouldApplyExtensions(this); - if (shouldApply != _extensionServerIsApplied[i]) { - ExtensionServer server = _extensionServers[i]; - if (shouldApply) { - // add extensions - foreach (Extension ext in extensionManager.CreateExtensions(server, this)) { - _extensions.Add(new ExtensionEntry(ext, server)); - } - } else { - // remove extensions - _extensions.RemoveAll( - delegate (ExtensionEntry entry) { - if (entry.Server == server) { - server.RemoveExtension(entry.Extension); - return true; - } else { - return false; - } - }); + if (_extensionServers[i] == server) { + bool shouldApply = server.ShouldApplyExtensions(this); + if (shouldApply != _extensionServerIsApplied[i]) { + _extensionServerIsApplied[i] = shouldApply; + ApplyUnapplyExtensionServer(extensionManager, shouldApply, server); } } } } + + private void ApplyUnapplyExtensionServer(ExtensionManager extensionManager, bool shouldApply, ExtensionServer server) + { + if (shouldApply) { + // add extensions + foreach (Extension ext in extensionManager.CreateExtensions(server, this)) { + _extensions.Add(new ExtensionEntry(ext, server)); + } + } else { + // remove extensions + _extensions.RemoveAll( + delegate (ExtensionEntry entry) { + if (entry.Server == server) { + server.RemoveExtension(entry.Extension); + return true; + } else { + return false; + } + }); + } + } #endregion #region Manage behavior diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Extensions/BehaviorExtension.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Extensions/BehaviorExtension.cs index 06633b7497..e0a8486733 100644 --- a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Extensions/BehaviorExtension.cs +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Extensions/BehaviorExtension.cs @@ -13,68 +13,9 @@ namespace ICSharpCode.WpfDesign.Extensions /// Base class for extensions that provide a behavior interface for the designed item. /// These extensions are always loaded. They must have an parameter-less constructor. /// - [ExtensionServer(typeof(BehaviorExtension.BehaviorExtensionServer))] - public abstract class BehaviorExtension : Extension + [ExtensionServer(typeof(DefaultExtensionServer.Permanent))] + public abstract class BehaviorExtension : DefaultExtension { - DesignItem _extendedItem; - /// - /// Gets the item that is being extended by the BehaviorExtension. - /// - public DesignItem ExtendedItem { - get { - if (_extendedItem == null) - throw new InvalidOperationException("Cannot access BehaviorExtension.ExtendedItem: " + - "The property is not initialized yet. Please move initialization logic " + - "that depends on ExtendedItem into the OnInitialized method."); - return _extendedItem; - } - } - - /// - /// Gets the design context of the extended item. "Context" is equivalent to "ExtendedItem.Context". - /// - public DesignContext Context { - get { - return this.ExtendedItem.Context; - } - } - - /// - /// Gets the service container of the extended item. "Services" is equivalent to "ExtendedItem.Services". - /// - public ServiceContainer Services { - get { - return this.ExtendedItem.Services; - } - } - - /// - /// Is called after the ExtendedItem was set. - /// Override this method to register your behavior with the item. - /// - protected virtual void OnInitialized() - { - } - - sealed class BehaviorExtensionServer : ExtensionServer - { - public override bool ShouldApplyExtensions(DesignItem extendedItem) - { - return true; - } - - public override Extension CreateExtension(Type extensionType, DesignItem extendedItem) - { - BehaviorExtension ext = (BehaviorExtension)Activator.CreateInstance(extensionType); - ext._extendedItem = extendedItem; - ext.OnInitialized(); - return ext; - } - - public override void RemoveExtension(Extension extension) - { - } - } } } diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Extensions/DefaultExtension.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Extensions/DefaultExtension.cs new file mode 100644 index 0000000000..5194230e95 --- /dev/null +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Extensions/DefaultExtension.cs @@ -0,0 +1,114 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Diagnostics; + +namespace ICSharpCode.WpfDesign.Extensions +{ + /// + /// Base class for extensions that have an parameter-less constructor and are initialized using the + /// OnInitialize method. + /// + public class DefaultExtension : Extension + { + DesignItem _extendedItem; + + /// + /// Gets the item that is being extended by the BehaviorExtension. + /// + public DesignItem ExtendedItem { + get { + if (_extendedItem == null) + throw new InvalidOperationException("Cannot access BehaviorExtension.ExtendedItem: " + + "The property is not initialized yet. Please move initialization logic " + + "that depends on ExtendedItem into the OnInitialized method."); + return _extendedItem; + } + } + + /// + /// Gets the design context of the extended item. "Context" is equivalent to "ExtendedItem.Context". + /// + public DesignContext Context { + get { + return this.ExtendedItem.Context; + } + } + + /// + /// Gets the service container of the extended item. "Services" is equivalent to "ExtendedItem.Services". + /// + public ServiceContainer Services { + get { + return this.ExtendedItem.Services; + } + } + + /// + /// Is called after the ExtendedItem was set. + /// Override this method to register your behavior with the item. + /// + protected virtual void OnInitialized() + { + } + + /// + /// Is called when the extension is removed. + /// + protected virtual void OnRemove() + { + } + + internal void CallOnRemove() { OnRemove(); } + + internal void InitializeDefaultExtension(DesignItem extendedItem) + { + Debug.Assert(this._extendedItem == null); + Debug.Assert(extendedItem != null); + + this._extendedItem = extendedItem; + OnInitialized(); + } + } + + /// + /// Base class for extension servers that create extensions that derive from . + /// + public abstract class DefaultExtensionServer : ExtensionServer + { + /// + /// Creates an instance of the DefaultExtension and calls OnInitialize on it. + /// + public override Extension CreateExtension(Type extensionType, DesignItem extendedItem) + { + DefaultExtension ext = (DefaultExtension)Activator.CreateInstance(extensionType); + ext.InitializeDefaultExtension(extendedItem); + return ext; + } + + /// + /// Calls OnRemove() on the DefaultExtension. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods")] + public override void RemoveExtension(Extension extension) + { + Debug.Assert(extension != null); + Debug.Assert(extension is DefaultExtension); + + ((DefaultExtension)extension).CallOnRemove(); + } + + internal sealed class Permanent : DefaultExtensionServer + { + public override bool ShouldApplyExtensions(DesignItem extendedItem) + { + return true; + } + } + } +} diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Extensions/ExtensionManager.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Extensions/ExtensionManager.cs index 0bb2576001..95bad14414 100644 --- a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Extensions/ExtensionManager.cs +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Extensions/ExtensionManager.cs @@ -32,8 +32,17 @@ namespace ICSharpCode.WpfDesign.Extensions void OnComponentRegistered(object sender, DesignItemEventArgs e) { - e.Item.SetExtensionServers(GetExtensionServersForItem(e.Item)); - e.Item.ApplyExtensions(this); + e.Item.SetExtensionServers(this, GetExtensionServersForItem(e.Item)); + } + + /// + /// Re-applies extensions from the ExtensionServer to the specified design items. + /// + public void ReapplyExtensions(IEnumerable items, ExtensionServer server) + { + foreach (DesignItem item in items) { + item.ReapplyExtensionServer(this, server); + } } #region Manage ExtensionEntries @@ -173,10 +182,11 @@ namespace ICSharpCode.WpfDesign.Extensions { Debug.Assert(extensionType != null); - foreach (ExtensionServerAttribute esa in extensionType.GetCustomAttributes(typeof(ExtensionServerAttribute), true)) { - return GetExtensionServer(esa); - } - throw new DesignerException("Extension types must have a [ExtensionServer] attribute."); + object[] extensionServerAttributes = extensionType.GetCustomAttributes(typeof(ExtensionServerAttribute), true); + if (extensionServerAttributes.Length != 1) + throw new DesignerException("Extension types must have exactly one [ExtensionServer] attribute."); + + return GetExtensionServer((ExtensionServerAttribute)extensionServerAttributes[0]); } /// diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Extensions/SelectionExtensionServer.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Extensions/SelectionExtensionServer.cs new file mode 100644 index 0000000000..76a602edc9 --- /dev/null +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Extensions/SelectionExtensionServer.cs @@ -0,0 +1,93 @@ +// +// +// +// +// $Revision$ +// + +using System; + +namespace ICSharpCode.WpfDesign.Extensions +{ + /// + /// Applies an extension to the selected components. + /// + public class SelectionExtensionServer : DefaultExtensionServer + { + /// + /// Is called after the extension server is initialized and the Context property has been set. + /// + protected override void OnInitialized() + { + base.OnInitialized(); + Services.Selection.SelectionChanged += OnSelectionChanged; + } + + void OnSelectionChanged(object sender, DesignItemCollectionEventArgs e) + { + Services.ExtensionManager.ReapplyExtensions(e.Items, this); + } + + /// + /// Gets if the item is selected. + /// + public override bool ShouldApplyExtensions(DesignItem extendedItem) + { + return Services.Selection.IsComponentSelected(extendedItem); + } + } + + /// + /// Applies an extension to the selected components, but not to the primary selection. + /// + public class SecondarySelectionExtensionServer : SelectionExtensionServer + { + /// + /// Gets if the item is in the secondary selection. + /// + public override bool ShouldApplyExtensions(DesignItem extendedItem) + { + return base.ShouldApplyExtensions(extendedItem) && Services.Selection.PrimarySelection != extendedItem; + } + } + + /// + /// Applies an extension to the primary selection. + /// + public class PrimarySelectionExtensionServer : DefaultExtensionServer + { + DesignItem oldPrimarySelection; + + /// + /// Is called after the extension server is initialized and the Context property has been set. + /// + protected override void OnInitialized() + { + base.OnInitialized(); + this.Services.Selection.PrimarySelectionChanged += OnPrimarySelectionChanged; + } + + void OnPrimarySelectionChanged(object sender, EventArgs e) + { + DesignItem newPrimarySelection = this.Services.Selection.PrimarySelection; + if (oldPrimarySelection != newPrimarySelection) { + if (oldPrimarySelection == null) { + this.Services.ExtensionManager.ReapplyExtensions(new DesignItem[] { newPrimarySelection }, this); + } else if (newPrimarySelection == null) { + this.Services.ExtensionManager.ReapplyExtensions(new DesignItem[] { oldPrimarySelection }, this); + } else { + this.Services.ExtensionManager.ReapplyExtensions(new DesignItem[] { oldPrimarySelection, newPrimarySelection }, this); + } + oldPrimarySelection = newPrimarySelection; + } + } + + /// + /// Gets if the item is the primary selection. + /// + public override bool ShouldApplyExtensions(DesignItem extendedItem) + { + return Services.Selection.PrimarySelection == extendedItem; + } + } +} diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Services.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Services.cs index c6f916327f..a44c5423f5 100644 --- a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Services.cs +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Services.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Windows; +using System.Windows.Media; namespace ICSharpCode.WpfDesign { diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Tools.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Tools.cs index 424d66fca1..e95641d582 100644 --- a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Tools.cs +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/Tools.cs @@ -6,8 +6,10 @@ // using System; +using System.Collections.Generic; using System.Windows.Input; using System.Windows; +using ICSharpCode.WpfDesign.Adorners; namespace ICSharpCode.WpfDesign { @@ -118,6 +120,10 @@ namespace ICSharpCode.WpfDesign /// DesignItem FindDesignedElementForOriginalSource(object originalSource); + /// + /// Gets the list of adorners displayed on the design panel. + /// + ICollection Adorners { get; } // The following members were missing in , but of course // are supported on the DesignPanel: diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/WpfDesign.csproj b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/WpfDesign.csproj index 649df1182b..3274ed8bee 100644 --- a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/WpfDesign.csproj +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign/Project/WpfDesign.csproj @@ -57,17 +57,23 @@ Configuration\GlobalAssemblyInfo.cs + + + + + + @@ -78,5 +84,6 @@ + \ No newline at end of file