HOW TO:建立 Surrogate 原則
更新:2007 年 11 月
下列程式碼範例示範如何實作衍生自 DockPanel 之類別的自訂 Surrogate 原則。
範例
本逐步解說示範如何建立 Surrogate 原則,提供選取項目的容器語意。DemoDockPanel 容器控制項使用這個原則,在其子系上提供其他工作及裝飾項。
Imports System
Imports System.Windows
' The DemoDockPanel control provides a DockPanel that
' has custom design-time behavior.
Public Class DemoDockPanel
Inherits System.Windows.Controls.DockPanel
End Class
using System;
using System.Windows;
namespace DemoControlLibrary
{
// The DemoDockPanel control provides a DockPanel that
// has custom design-time behavior.
public class DemoDockPanel : System.Windows.Controls.DockPanel
{
}
}
Imports System
Imports System.Collections.Generic
Imports System.Windows
Imports System.Windows.Input
Imports System.Windows.Media
Imports System.Windows.Shapes
Imports Microsoft.Windows.Design.Interaction
Imports Microsoft.Windows.Design.Model
Imports Microsoft.Windows.Design.Policies
' The DockPanelAdornerProvider class implements an adorner
' that you can use to set the Margin property by using a
' drag operation. The DockPanelPolicy class enables a
' container policy for offering additional tasks and
' adorners on the panel's children.
<UsesItemPolicy(GetType(DockPanelPolicy))> _
Class DockPanelAdornerProvider
Inherits AdornerProvider
Public Sub New()
' The adorner is a Rectangle element.
Dim r As New Rectangle()
r.Width = 23.0
r.Height = 23.0
r.Fill = AdornerColors.GlyphFillBrush
' Set the rectangle's placement in the adorner panel.
Dim placement As New AdornerPlacementCollection()
placement.PositionRelativeToAdornerWidth(-1, 0)
placement.SizeRelativeToAdornerDesiredHeight(1.0, 0)
placement.SizeRelativeToAdornerDesiredWidth(1.0, 0)
placement.PositionRelativeToAdornerHeight(-1.0, 0)
AdornerPanel.SetPlacements(r, placement)
Dim p As New AdornerPanel()
p.Children.Add(r)
AdornerPanel.SetTask(r, New DockPanelMarginTask())
Adorners.Add(p)
End Sub
End Class
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using Microsoft.Windows.Design.Interaction;
using Microsoft.Windows.Design.Model;
using Microsoft.Windows.Design.Policies;
namespace DemoControlLibrary.VisualStudio.Design
{
// The DockPanelAdornerProvider class implements an adorner
// that you can use to set the Margin property by using a
// drag operation. The DockPanelPolicy class enables a
// container policy for offering additional tasks and
// adorners on the panel's children.
[UsesItemPolicy(typeof(DockPanelPolicy))]
class DockPanelAdornerProvider : AdornerProvider
{
public DockPanelAdornerProvider()
{
// The adorner is a Rectangle element.
Rectangle r = new Rectangle();
r.Width = 23.0;
r.Height = 23.0;
r.Fill = AdornerColors.GlyphFillBrush;
// Set the rectangle's placement in the adorner panel.
AdornerPlacementCollection placement = new AdornerPlacementCollection();
placement.PositionRelativeToAdornerWidth(-1, 0);
placement.SizeRelativeToAdornerDesiredHeight(1.0, 0);
placement.SizeRelativeToAdornerDesiredWidth(1.0, 0);
placement.PositionRelativeToAdornerHeight(-1.0, 0);
AdornerPanel.SetPlacements(r, placement);
AdornerPanel p = new AdornerPanel();
p.Children.Add(r);
AdornerPanel.SetTask(r, new DockPanelMarginTask());
Adorners.Add(p);
}
}
}
Imports System
Imports System.Windows.Controls
Imports Microsoft.Windows.Design.Interaction
Imports Microsoft.Windows.Design.Model
Imports Microsoft.Windows.Design.Policies
Imports System.Windows
' The DockPanelContextMenuProvider class implements a
' context menu group for setting the Dock property of
' the target control. The menu feature provider is bound to
' a policy that enables a context menu for children of
' a DemoDockPanel.
<UsesItemPolicy(GetType(DockPanelPolicy))> _
Class DockPanelContextMenuProvider
Inherits ContextMenuProvider
Private lastFill As MenuAction
Public Sub New()
' Create and populate the menu group.
Dim group As New MenuGroup("Dock", "Dock")
group.Items.Add(New DockMenuAction(Dock.Left))
group.Items.Add(New DockMenuAction(Dock.Right))
group.Items.Add(New DockMenuAction(Dock.Top))
group.Items.Add(New DockMenuAction(Dock.Bottom))
lastFill = New MenuAction("Last Child Fill")
lastFill.Checkable = True
AddHandler lastFill.Execute, AddressOf lastFill_Execute
group.Items.Add(lastFill)
AddHandler UpdateItemStatus, AddressOf DockPanelContextMenu_UpdateItemStatus
' The group appears in a flyout menu.
group.HasDropDown = True
Items.Add(group)
End Sub
Sub DockPanelContextMenu_UpdateItemStatus(ByVal sender As Object, ByVal e As MenuActionEventArgs)
Dim parent As ModelItem = e.Selection.PrimarySelection.Parent
Dim check As Boolean = CBool(parent.Properties(DockPanel.LastChildFillProperty).ComputedValue)
lastFill.Checked = check
End Sub
Sub lastFill_Execute(ByVal sender As Object, ByVal e As MenuActionEventArgs)
Dim parent As ModelItem = e.Selection.PrimarySelection.Parent
If lastFill.Checked Then
parent.Properties(DockPanel.LastChildFillProperty).ClearValue()
Else
parent.Properties(DockPanel.LastChildFillProperty).SetValue(lastFill.Checked)
End If
End Sub
Private Class DockMenuAction
Inherits MenuAction
Private dockValue As Dock
Friend Sub New(ByVal value As Dock)
MyBase.New(value.ToString())
dockValue = value
AddHandler Execute, AddressOf OnExecute
End Sub
Private Sub OnExecute(ByVal sender As Object, ByVal args As MenuActionEventArgs)
Dim item As ModelItem = args.Selection.PrimarySelection
item.Properties(DockPanel.DockProperty).SetValue(dockValue)
End Sub
End Class
End Class
using System;
using System.Windows.Controls;
using Microsoft.Windows.Design.Interaction;
using Microsoft.Windows.Design.Model;
using Microsoft.Windows.Design.Policies;
using System.Windows;
namespace DemoControlLibrary.VisualStudio.Design
{
// The DockPanelContextMenuProvider class implements a
// context menu group for setting the Dock property of
// the target control. The menu feature provider is bound to
// a policy that enables a context menu for children of
// a DemoDockPanel.
[UsesItemPolicy(typeof(DockPanelPolicy))]
class DockPanelContextMenuProvider : ContextMenuProvider
{
MenuAction lastFill;
public DockPanelContextMenuProvider()
{
// Create and populate the menu group.
MenuGroup group = new MenuGroup("Dock", "Dock");
group.Items.Add(new DockMenuAction(Dock.Left));
group.Items.Add(new DockMenuAction(Dock.Right));
group.Items.Add(new DockMenuAction(Dock.Top));
group.Items.Add(new DockMenuAction(Dock.Bottom));
lastFill = new MenuAction("Last Child Fill");
lastFill.Checkable = true;
lastFill.Execute +=
new EventHandler<MenuActionEventArgs>(lastFill_Execute);
group.Items.Add(lastFill);
UpdateItemStatus +=
new EventHandler<MenuActionEventArgs>(
DockPanelContextMenu_UpdateItemStatus);
// The group appears in a flyout menu.
group.HasDropDown = true;
Items.Add(group);
}
void DockPanelContextMenu_UpdateItemStatus(
object sender,
MenuActionEventArgs e)
{
ModelItem parent = e.Selection.PrimarySelection.Parent;
bool check = (bool)parent.Properties[DockPanel.LastChildFillProperty].ComputedValue;
lastFill.Checked = check;
}
void lastFill_Execute(object sender, MenuActionEventArgs e)
{
ModelItem parent = e.Selection.PrimarySelection.Parent;
if (lastFill.Checked)
{
parent.Properties[DockPanel.LastChildFillProperty].ClearValue();
}
else
{
parent.Properties[DockPanel.LastChildFillProperty].SetValue(lastFill.Checked);
}
}
private class DockMenuAction : MenuAction
{
private Dock dockValue;
internal DockMenuAction(Dock value) : base(value.ToString())
{
dockValue = value;
Execute += OnExecute;
}
private void OnExecute(object sender, MenuActionEventArgs args)
{
ModelItem item = args.Selection.PrimarySelection;
item.Properties[DockPanel.DockProperty].SetValue(dockValue);
}
}
}
}
Imports System
Imports System.Collections.Generic
Imports System.Windows
Imports System.Windows.Input
Imports Microsoft.Windows.Design.Interaction
' A DockPanelMarginTask is attached to to the adorner
' offered by the DockPanelAdornerProvider class. When
' you drag the adorner, the target control's Margin
' property changes.
Class DockPanelMarginTask
Inherits Task
Private dragBinding, endDragBinding As InputBinding
Private initialMargin As Thickness
' The DockPanelMarginTask constructor establishes mappings
' between user inputs and commands.
Public Sub New()
Dim beginDrag As New ToolCommand("BeginDrag")
Dim drag As New ToolCommand("Drag")
Dim endDrag As New ToolCommand("EndDrag")
Dim resetMargins As New ToolCommand("ResetMargins")
Me.InputBindings.Add(New InputBinding( _
beginDrag, _
New ToolGesture(ToolAction.DragIntent, MouseButton.Left)))
Me.InputBindings.Add( _
New InputBinding( _
resetMargins, _
New ToolGesture(ToolAction.DoubleClick, MouseButton.Left)))
Me.dragBinding = New InputBinding( _
drag, _
New ToolGesture(ToolAction.Move))
Me.endDragBinding = New InputBinding( _
endDrag, _
New ToolGesture(ToolAction.DragComplete))
Me.ToolCommandBindings.Add(New ToolCommandBinding(beginDrag, AddressOf OnBeginDrag))
Me.ToolCommandBindings.Add(New ToolCommandBinding(drag, AddressOf OnDrag))
Me.ToolCommandBindings.Add(New ToolCommandBinding(endDrag, AddressOf OnEndDrag))
Me.ToolCommandBindings.Add(New ToolCommandBinding(resetMargins, AddressOf OnResetMargins))
End Sub
Private Sub OnBeginDrag(ByVal sender As Object, ByVal args As ExecutedToolEventArgs)
Dim data As GestureData = GestureData.FromEventArgs(args)
Me.BeginFocus(data)
Me.InputBindings.Add(dragBinding)
Me.InputBindings.Add(endDragBinding)
Me.initialMargin = CType(data.ImpliedSource.Properties(FrameworkElement.MarginProperty).ComputedValue, Thickness)
End Sub
Private Sub OnDrag(ByVal sender As Object, ByVal args As ExecutedToolEventArgs)
Dim data As MouseGestureData = MouseGestureData.FromEventArgs(args)
Dim offX As Double = data.PositionDelta.X
Dim offY As Double = data.PositionDelta.Y
Dim newMargin As Thickness = initialMargin
newMargin.Bottom += offY
newMargin.Top += offY
newMargin.Left += offX
newMargin.Right += offX
data.ImpliedSource.Properties(FrameworkElement.MarginProperty).SetValue(newMargin)
End Sub
Private Sub OnEndDrag(ByVal sender As Object, ByVal args As ExecutedToolEventArgs)
Description = "Adjust margin"
Me.Complete()
End Sub
Protected Overrides Sub OnCompleted(ByVal e As EventArgs)
Me.Cleanup()
MyBase.OnCompleted(e)
End Sub
Protected Overrides Sub OnReverted(ByVal e As EventArgs)
Me.Cleanup()
MyBase.OnReverted(e)
End Sub
Private Sub Cleanup()
Me.InputBindings.Remove(dragBinding)
Me.InputBindings.Remove(endDragBinding)
End Sub
Private Sub OnResetMargins(ByVal sender As Object, ByVal args As ExecutedToolEventArgs)
Dim data As GestureData = GestureData.FromEventArgs(args)
data.ImpliedSource.Properties(FrameworkElement.MarginProperty).ClearValue()
End Sub
End Class
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using Microsoft.Windows.Design.Interaction;
namespace DemoControlLibrary.VisualStudio.Design
{
// A DockPanelMarginTask is attached to to the adorner
// offered by the DockPanelAdornerProvider class. When
// you drag the adorner, the target control's Margin
// property changes.
class DockPanelMarginTask : Task
{
InputBinding dragBinding, endDragBinding;
Thickness initialMargin;
// The DockPanelMarginTask constructor establishes mappings
// between user inputs and commands.
public DockPanelMarginTask()
{
ToolCommand beginDrag = new ToolCommand("BeginDrag");
ToolCommand drag = new ToolCommand("Drag");
ToolCommand endDrag = new ToolCommand("EndDrag");
ToolCommand resetMargins = new ToolCommand("ResetMargins");
this.InputBindings.Add(
new InputBinding(
beginDrag,
new ToolGesture(ToolAction.DragIntent, MouseButton.Left)));
this.InputBindings.Add(
new InputBinding(
resetMargins,
new ToolGesture(ToolAction.DoubleClick, MouseButton.Left)));
this.dragBinding = new InputBinding(
drag,
new ToolGesture(ToolAction.Move));
this.endDragBinding = new InputBinding(
endDrag,
new ToolGesture(ToolAction.DragComplete));
this.ToolCommandBindings.Add(
new ToolCommandBinding(beginDrag, OnBeginDrag));
this.ToolCommandBindings.Add(
new ToolCommandBinding(drag, OnDrag));
this.ToolCommandBindings.Add(
new ToolCommandBinding(endDrag, OnEndDrag));
this.ToolCommandBindings.Add(
new ToolCommandBinding(resetMargins, OnResetMargins));
}
private void OnBeginDrag(object sender, ExecutedToolEventArgs args)
{
GestureData data = GestureData.FromEventArgs(args);
this.BeginFocus(data);
this.InputBindings.Add(dragBinding);
this.InputBindings.Add(endDragBinding);
this.initialMargin = (Thickness)data.ImpliedSource.Properties[
FrameworkElement.MarginProperty].ComputedValue;
}
private void OnDrag(object sender, ExecutedToolEventArgs args)
{
MouseGestureData data = MouseGestureData.FromEventArgs(args);
double offX = data.PositionDelta.X;
double offY = data.PositionDelta.Y;
Thickness newMargin = initialMargin;
newMargin.Bottom += offY;
newMargin.Top += offY;
newMargin.Left += offX;
newMargin.Right += offX;
data.ImpliedSource.Properties[FrameworkElement.MarginProperty].SetValue(newMargin);
}
private void OnEndDrag(object sender, ExecutedToolEventArgs args)
{
Description = "Adjust margin";
this.Complete();
}
protected override void OnCompleted(EventArgs e)
{
this.Cleanup();
base.OnCompleted(e);
}
protected override void OnReverted(EventArgs e)
{
this.Cleanup();
base.OnReverted(e);
}
private void Cleanup()
{
this.InputBindings.Remove(dragBinding);
this.InputBindings.Remove(endDragBinding);
}
private void OnResetMargins(object sender, ExecutedToolEventArgs args)
{
GestureData data = GestureData.FromEventArgs(args);
data.ImpliedSource.Properties[FrameworkElement.MarginProperty].ClearValue();
}
}
}
Imports System
Imports System.Collections.Generic
Imports Microsoft.Windows.Design.Model
Imports Microsoft.Windows.Design.Policies
' The DockPanelPolicy class implements a surrogate policy that
' provides container semantics for a selected item. By using
' this policy, the DemoDockPanel container control offers
' additional tasks and adorners on its children.
Class DockPanelPolicy
Inherits PrimarySelectionPolicy
Public Overrides ReadOnly Property IsSurrogate() As Boolean
Get
Return True
End Get
End Property
Public Overrides Function GetSurrogateItems( _
ByVal item As Microsoft.Windows.Design.Model.ModelItem) _
As System.Collections.Generic.IEnumerable( _
Of Microsoft.Windows.Design.Model.ModelItem)
Dim parent As ModelItem = item.Parent
Dim e As New System.Collections.Generic.List(Of ModelItem)
If (parent IsNot Nothing) Then
e.Add(parent)
End If
Return e
End Function
End Class
using System;
using System.Collections.Generic;
using Microsoft.Windows.Design.Model;
using Microsoft.Windows.Design.Policies;
namespace DemoControlLibrary.VisualStudio.Design
{
// The DockPanelPolicy class implements a surrogate policy that
// provides container semantics for a selected item. By using
// this policy, the DemoDockPanel container control offers
// additional tasks and adorners on its children.
class DockPanelPolicy : PrimarySelectionPolicy
{
public override bool IsSurrogate
{
get
{
return true;
}
}
public override IEnumerable<ModelItem> GetSurrogateItems(ModelItem item)
{
ModelItem parent = item.Parent;
if (parent != null)
{
yield return parent;
}
}
}
}
Imports System
Imports DemoControlLibrary
Imports Microsoft.Windows.Design.Metadata
Imports Microsoft.Windows.Design.Features
' Metadata in Cider can be created programmatically instead
' of requiring that it be compiled right on a control. This
' allows you to keep your design time and run time separated.
Friend Class Metadata
Implements IRegisterMetadata
Public Sub Register() Implements IRegisterMetadata.Register
Dim builder As New AttributeTableBuilder()
InitializeAttributes(builder)
MetadataStore.AddAttributeTable(builder.CreateTable())
End Sub
Private Sub InitializeAttributes(ByVal builder As AttributeTableBuilder)
builder.AddCallback(GetType(DemoDockPanel), AddressOf AddDockPanelAttributes)
End Sub
Private Sub AddDockPanelAttributes(ByVal builder As AttributeCallbackBuilder)
builder.AddCustomAttributes( _
New FeatureAttribute(GetType(DockPanelAdornerProvider)), _
New FeatureAttribute(GetType(DockPanelContextMenuProvider)))
End Sub
End Class
using System;
using DemoControlLibrary;
using Microsoft.Windows.Design.Metadata;
using Microsoft.Windows.Design.Features;
namespace DemoControlLibrary.VisualStudio.Design
{
/// <summary>
/// Metadata in Cider can be created programmatically instead
/// of requiring that it be compiled right on a control. This
/// allows you to keep your design time and run time separated.
/// </summary>
internal class Metadata : IRegisterMetadata
{
public void Register()
{
AttributeTableBuilder builder = new AttributeTableBuilder();
InitializeAttributes(builder);
MetadataStore.AddAttributeTable(builder.CreateTable());
}
private void InitializeAttributes(AttributeTableBuilder builder)
{
builder.AddCallback(typeof(DemoDockPanel), AddDockPanelAttributes);
}
private void AddDockPanelAttributes(AttributeCallbackBuilder builder)
{
builder.AddCustomAttributes(
new FeatureAttribute(typeof(DockPanelAdornerProvider)),
new FeatureAttribute(typeof(DockPanelContextMenuProvider))
);
}
}
}
<Window x:Class="Window1"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:DemoControlLibrary;assembly=DemoControlLibrary"
Title="Window1" Height="300" Width="300">
<Grid>
<c:DemoDockPanel>
<Button />
<Button />
</c:DemoDockPanel>
</Grid>
</Window>
<Window x:Class="DemoApplication.Window1"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:DemoControlLibrary;assembly=DemoControlLibrary"
Title="Window1" Height="300" Width="300">
<Grid>
<c:DemoDockPanel>
<Button />
<Button />
</c:DemoDockPanel>
</Grid>
</Window>
編譯程式碼
在三個不同的組件中,編譯先前的範例程式碼。
編譯自訂控制項
建立 DemoDockPanel 類別。
加入下列組件的參考。
PresentationCore
PresentationFramework
WindowsBase
在名為 DemoControlLibrary 的組件中編譯 DemoDockPanel 類別。
vbc /r:PresentationCore.dll /r:PresentationFramework.dll /r:WindowsBase.dll /t:library /out:DemoControlLibrary.dll DemoDockPanel.vb
csc /r:PresentationCore.dll /r:PresentationFramework.dll /r:WindowsBase.dll /t:library /out:DemoControlLibrary.dll DemoDockPanel.cs
編譯自訂中繼資料
建立下列類別。
DockPanelAdornerProvider
DockPanelContextMenuProvider
DockPanelMarginTask
DockPanelPolicy
Metadata
加入下列組件的參考:
PresentationCore
PresentationFramework
WindowsBase
Microsoft.Windows.Design
Microsoft.Windows.Design.Extensibility
Microsoft.Windows.Design.Interaction
加入 DemoControlLibrary 組件或專案的參考。
在名為 DemoControlLibrary.VisualStudio.Design 的不同組件中,編譯先前的類別。將編譯過的組件導向至 DemoControlLibrary 組件的相同資料夾。
vbc /r:PresentationCore.dll /r:PresentationFramework.dll /r:WindowsBase.dll /r:Microsoft.Windows.Design.dll /r:Microsoft.Windows.Design.Extensibility.dll /r:Microsoft.Windows.Design.Interaction.dll /r:DemoControlLibrary.dll /t:library /out:DemoControlLibrary.VisualStudio.Design.dll DockPanelAdornerProvider.vb DockPanelContextMenuProvider.vb DockPanelMarginTask.vb DockPanelPolicy.vb Metadata.vb
csc /r:PresentationCore.dll /r:PresentationFramework.dll /r:WindowsBase.dll /r:Microsoft.Windows.Design.dll /r:Microsoft.Windows.Design.Extensibility.dll /r:Microsoft.Windows.Design.Interaction.dll /r:DemoControlLibrary.dll /t:library /out:DemoControlLibrary.VisualStudio.Design.dll DockPanelAdornerProvider.cs DockPanelContextMenuProvider.cs DockPanelMarginTask.cs DockPanelPolicy.cs Metadata.cs
編譯測試應用程式
在 Visual Studio 中,建立新的 WPF 應用程式專案。
加入 DemoControlLibrary 組件或專案的參考。
以稍早所列的 XAML 取代 Window1.xaml 中現有的 XAML。
在 [設計] 檢視中,以滑鼠右鍵按一下按鈕,然後指向內容功能表上的 [停駐],設定 Dock 附加屬性。