Tipps und Tricks

Frage (552) zu VB(14).NET (VS2015), WPF:

WPF-Nutzersteuerelement mit zusätzlichen Eigenschaften, die im Eigenschaftsfenster des Visual Studio bearbeitet werden.

Antwort:
Wenn man ein WPF-Nutzersteuerelement mit Eigenschaften erweitert, dann kann man, wenn man das Steuerelement  im XAML nutzt, diese Eigenschaften im Eigenschaftsfenster bearbeiten. Es kann jedoch erforderlich sein, dass die Standardbearbeitung (z.B. TextBox im Eigenschaftsfenster) für eine effektive Arbeitsweise nicht ausreicht. Dafür bietet das Visual Studio Bibliotheken, mit denen man Funktionen zur Bearbeitung einer Eigenschaft für das konkrete Steuerelement hinzufügen kann. Diese Möglichkeit bietet auch die Comminity Edition des Visual Studio 2015. Im folgenden Beispiel werden die erforderlichen Schritte beschrieben.

1. Bibliothek für UserControls anlegen (im Beispiel \fs20 WpfControlLibrary1)\fs20
2. UserControl aufbauen (im Beispiel UserControl1)
3. Im XAML ist im vorliegenden Beispiel ein Label-Steuerelement vorhanden, in dem der Modus des Steuerelementes beispielhaft angezeigt wird:

[xml]

<UserControl x:Class="UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WpfControlLibrary1"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
  <Grid>
    <Label Name="lbl"/>
  </Grid>
</UserControl>
[/xml]

4. Im CodeBehind das UserControls Code für Eigenschaft hinzufügen (z.B. Eigenschaften "Eigenschaft " vom Typ "Guid" und "Filename" vom Typ String)

[vb]
Imports System.ComponentModel

Public Class UserControl1

  Public Sub New()

    ' This call is required by the designer.
    InitializeComponent()

    ' Add any initialization after the InitializeComponent() call.
    If System.ComponentModel.DesignerProperties.GetIsInDesignMode(Me) Then
      Me.lbl.Content = "Entwickler-Modus"
    Else
      Me.lbl.Content = "Ausführungs-Modus"
    End If

  End Sub

  Public Shared ReadOnly FileNameProperty As DependencyProperty = DependencyProperty.Register(
        "FileName",
        GetType(String),
        GetType(UserControl1),
        New PropertyMetadata("File name not set."))

  <Description("Dateiname eintragen")>
  <Category("Meine Eigenschaften")>
  Public Property FileName() As String
    Get
      Return CType(Me.GetValue(EigenschaftProperty), String)
    End Get
    Set(ByVal value As String)
      Me.SetValue(EigenschaftProperty, value)
    End Set
  End Property

  Public Shared ReadOnly EigenschaftProperty As DependencyProperty =
    DependencyProperty.Register("Eigenschaft", GetType(Guid), GetType(UserControl1), New PropertyMetadata(Nothing))

  <Description("Neue GUID eintragen")>
  <Category("Meine Eigenschaften")>
  <DefaultValue(GetType(Guid), "00000000-0000-0000-0000-000000000000")>
  Public Property Eigenschaft() As Guid
    Get
      Return CType(Me.GetValue(EigenschaftProperty), Guid)
    End Get
    Set(ByVal value As Guid)
      Me.SetValue(EigenschaftProperty, value)
    End Set
  End Property

End Class
[/vb]

5. Genutzt werden kann das Steuerelement im Hauptprogramm in üblicher Weise (natürlich Verweis auf die Steuerelementebibliothek setzen):

[xml]
<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication1"
        xmlns:uc="clr-namespace:WpfControlLibrary1;assembly=WpfControlLibrary1"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
  <Grid>
    <uc:UserControl1/>
  </Grid>
</Window>
[/xml]

Es können im Eigenschaftsfenster mit den üblichen Darstellungen diese Eigenschaften bearbeitet werden, was für den Fall einer Guid recht umständlich sein kein (separater Guid-Generator). Deshalb wird ein Designer zur Bearbeitung der Eigenschaftswerte im Eigenschaftsfenster aufgebaut:

6. Bibliothek für design des UserControls anlegen (im Beispiel WpfControlLibrary1.Design)
7. Projekteigenschaften ändern:

7.1 Root Namespace löschen, um spätere Konflikte zu vermeiden. Alle Programmteile müssen dann explizit den Namensraum enthalten.
7.2 Ausgabepfad für das Übersetzungsergebnis auf den Ausgabepfad der betreffenden Bibliothek für UserControls setzen, damit beide Assemblys im gleichen Verzeichnis liegen
7.3 Target CPU auf X86 setzen, da das Visual Studio im Modus x86 arbeitet und die genutzten Bibliotheken dafür ausgelegt sind. Damit werden Warnungen im Error Log verhindert.
7.4 Referenzen für 2 benötigte Bibliotheken des Visual Studio einfügen: Microsoft.Windows.Design.Extensibility.dll und Microsoft.Windows.Design.Interaction.dll.
 Diese befinden sich standardmäßig im Pfad C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\PublicAssemblies
 Zu beachten ist, dass jede Version des Visual Studios seine eigenen Bibliotheken mitbringt. Das betreifft auch die verschiedenen Servicepacks (z.B. im Visual Studio 2013).
7.5 Referenz auf die Nutzersteuerbibliothek setzen, da in den Metadaten die Designerfunktionalität mit den Eigenschaften des Nutzersteuerelementes verknüpft werden.

8. In einem Resourcen-Wörterbuch (im Beispiel \fs20 EditorResources) \fs20 das Design als Datatemplate ablegen:

[xml]
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:PropertyEditing="clr-namespace:Microsoft.Windows.Design.PropertyEditing;assembly=Microsoft.Windows.Design.Interaction"
                    xmlns:Local="clr-namespace:WpfControlLibrary1.Design"
                    x:Class="WpfControlLibrary1.Design.EditorResources">
  <DataTemplate x:Key="EigenschaftsEditorTemplate">
    <Grid>
      <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition />
      </Grid.RowDefinitions>
      <TextBox Grid.Row="0" Text="{Binding StringValue}" Margin="2"/>
      <PropertyEditing:EditModeSwitchButton Grid.Row="1" Content="New Guid" ToolTip="Neue GUID erzeugen." Margin="2"/>
    </Grid>
  </DataTemplate>

  <DataTemplate x:Key="FileBrowserInlineEditorTemplate">
    <Grid>
      <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition />
      </Grid.RowDefinitions>
      <TextBox Grid.Row="0" Text="{Binding StringValue}" Margin="2"/>
      <PropertyEditing:EditModeSwitchButton Grid.Row="1" Content="Neuer Dateiname" ToolTip="Neuen Dateinamen holen." Margin="2"/>
    </Grid>
  </DataTemplate>

</ResourceDictionary>
[/xml]

Wichtig sind die Verweise auf \fs20 PropertyEditing=Microsoft.Windows.Design.Interaction und x:Class=EditorResources, was die dazugehörende Klasse für die Nutzung des Datatemplates im Code ist.

9. Klasse für den Zugriff auf die Datatemplates anlegen (im Beispiel EditorResources):

[vb]
Namespace WpfControlLibrary1.Design

  Partial Public Class EditorResources
    Inherits ResourceDictionary

    Public Sub New()
      MyBase.New
      Me.InitializeComponent()
    End Sub
  End Class

End Namespace
[/vb]

10. Für jede Eigenschaft einen EigenschaftsEditor erstellen, der das Template bereitstellt und die Verarbeitung der Nutzeraktivitäten ausführt:

Beispiel für Guid-Eigenschaft:

[vb]
Imports Microsoft.Windows.Design.PropertyEditing

Namespace WpfControlLibrary1.Design

  Public Class EigenschaftsEditor
    Inherits DialogPropertyValueEditor

    Private res As EditorResources = New EditorResources()

    Public Sub New()
      Me.InlineEditorTemplate = TryCast(res("EigenschaftsEditorTemplate"), DataTemplate)
    End Sub

    Public Overrides Sub ShowDialog(propertyValue As PropertyValue, commandSource As IInputElement)
      propertyValue.StringValue = Guid.NewGuid().ToString()
    End Sub

  End Class

End Namespace
[/vb]

Beispiel für Filename-Eigenschaft:

[vb]
Imports Microsoft.Windows.Design.PropertyEditing
Imports Microsoft.Win32

Namespace WpfControlLibrary1.Design

  Public Class FileBrowserDialogPropertyValueEditor
    Inherits DialogPropertyValueEditor

    Private res As EditorResources = New EditorResources()

    Public Sub New()
      Me.InlineEditorTemplate = TryCast(res("FileBrowserInlineEditorTemplate"), DataTemplate)
    End Sub

    Public Overrides Sub ShowDialog(propertyValue As PropertyValue, commandSource As IInputElement)
      Dim ofd As New OpenFileDialog() With {.Multiselect = False}
      If ofd.ShowDialog() Then propertyValue.StringValue = ofd.FileName
    End Sub
  End Class
End Namespace
[/vb]

11. Zum Schluss noch die Klasse für die Verknüpfung der Metainformation:

[vb]
'C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\PublicAssemblies

Imports Microsoft.Windows.Design.Metadata
Imports Microsoft.Windows.Design.PropertyEditing

<Assembly: ProvideMetadata(GetType(WpfControlLibrary1.Design.Metadata))>
Namespace WpfControlLibrary1.Design
  Public Class Metadata
    Implements IProvideAttributeTable

    Private ReadOnly Property AttributeTable As AttributeTable Implements IProvideAttributeTable.AttributeTable
      Get
        Dim builder As AttributeTableBuilder = New AttributeTableBuilder()

        builder.AddCustomAttributes(GetType(WpfControlLibrary1.UserControl1),
                                    "FileName",
                                    PropertyValueEditor.CreateEditorAttribute(
                                    GetType(FileBrowserDialogPropertyValueEditor)))

        builder.AddCustomAttributes(GetType(WpfControlLibrary1.UserControl1),
                                    "Eigenschaft",
                                    PropertyValueEditor.CreateEditorAttribute(
                                    GetType(EigenschaftsEditor)))

        Return builder.CreateTable()
      End Get
    End Property

  End Class
End Namespace
[/vb]

Nach der Übersetzung können die Eigenschaften im Eigenschaftsfenster bearbeitet werden. Falls die Bearbeitungsmöglichkeit (Buttons im vorliegenden Beispiel) nicht angezeigt wird, ist das Visual Studio zu beenden und neu zu starten. Das kann beispielsweise der Fall sein, wenn im Steuerelement bzw. im Desigenrcode Änderungen durchgeführt wurden und die Assembblys nicht mehr zueinander passen.
\fs20

Stand des Beitrages: 02.02.16 06:07, zuletzt geändert: 02.02.16 06:53



Bitte wählen sie den Themenbereich aus
Bitte geben sie einen Suchbegriff ein

Die hier dargestellten Tipps und Tricks sind das Ergebnis selbst ersteller Lösungsvarianten, die für Projekte und Schulungen erarbeitet wurden.