Condividi tramite


Procedura: creare un criterio sostitutivo

Aggiornamento: novembre 2007

Nell'esempio di codice seguente viene illustrato come implementare un criterio sostitutivo personalizzato per una classe derivata da DockPanel.

Esempio

In questa procedura dettagliata viene illustrato come creare un criterio sostitutivo che fornisce la semantica di contenitore per un elemento selezionato. Utilizzando questo criterio, il controllo contenitore DemoDockPanel offre attività e strumenti decorativi aggiuntivi sui propri elementi figlio.

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>

Compilazione del codice

Compilare il codice dell'esempio precedente in tre assembly distinti.

Compilazione del controllo personalizzato

  1. Creare la classe DemoDockPanel.

  2. Aggiungere riferimenti agli assembly riportati di seguito.

    • PresentationCore

    • PresentationFramework

    • WindowsBase

  3. Compilare la classe DemoDockPanel in un assembly denominato DemoControlLibrary.

    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
    

Compilazione dei metadati personalizzati

  1. Creare le classi riportate di seguito.

    • DockPanelAdornerProvider

    • DockPanelContextMenuProvider

    • DockPanelMarginTask

    • DockPanelPolicy

    • Metadata

  2. Aggiungere riferimenti agli assembly riportati di seguito:

    • PresentationCore

    • PresentationFramework

    • WindowsBase

    • Microsoft.Windows.Design

    • Microsoft.Windows.Design.Extensibility

    • Microsoft.Windows.Design.Interaction

  3. Aggiungere un riferimento all'assembly o progetto DemoControlLibrary.

  4. Compilare le classi precedenti in un assembly distinto denominato DemoControlLibrary.VisualStudio.Design. Indirizzare l'assembly compilato nella stessa cartella dell'assembly 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
    

Compilazione dell'applicazione di test

  1. In Visual Studio creare un nuovo progetto di applicazione WPF.

  2. Aggiungere un riferimento all'assembly o progetto DemoControlLibrary.

  3. Nel file Window1.xaml sostituire il codice XAML esistente con quello indicato in precedenza.

    In visualizzazione Progettazione fare clic con il pulsante destro del mouse sul pulsante e scegliere Dock dal menu di scelta rapida per impostare la proprietà connessa Dock.

Vedere anche

Riferimenti

ItemPolicy

PrimarySelectionPolicy

Altre risorse

Concetti avanzati sulla estensibilità

Estensibilità di Progettazione WPF