Freigeben über


Exemplarische Vorgehensweise: Erstellen einer Shell-Erweiterung

Diese exemplarische Vorgehensweise veranschaulicht, wie eine Shellerweiterung für LightSwitch erstellt wird. Die Shell für eine LightSwitch-Anwendung ermöglicht es Benutzern, mit der Anwendung zu interagieren. Sie manifestiert die navigierbaren Elemente, ausgeführten Bildschirme, zugeordneten Befehle, aktuellen Benutzerinformationen und andere nützliche Informationen, die Bestandteil der Shelldomäne sind. LightSwitch bietet eine direkte und leistungsfähige Shell. Sie können jedoch auch eine eigene Shell erstellen, die Ihre eigenen kreativen Möglichkeiten zur Interaktion mit den verschiedenen Segmenten der LightSwitch-Anwendung bereitstellt.

In dieser exemplarischen Vorgehensweise erstellen Sie eine Shell, die der Standardshell ähnelt, aber einige feine Unterschiede in Darstellung und Verhalten aufweist. Die Befehlsleiste eliminiert die Befehlsgruppen und verschiebt die Schaltfläche Bildschirm entwerfen nach links. Das Navigationsmenü ist fixiert. Es wird kein Startbildschirm angezeigt, und die Bildschirme werden durch Doppelklick auf die Menüelemente geöffnet. Die Bildschirme implementieren einen unterschiedlichen Validierungsindikator, und aktuelle Benutzerinformationen werden immer in der unteren linken Ecke der Shell angezeigt. Anhand dieser Unterschiede lassen sich mehrere nützliche Techniken für die Erstellung von Shellerweiterungen veranschaulichen.

Die Struktur einer Shellerweiterung umfasst drei Hauptkomponenten:

  • Das Managed Extensibility Framework (MEF), das die Implementierung des Shellvertrags exportiert.

  • Die Extensible Application Markup Language (XAML), die die Steuerelemente beschreibt, die für die Benutzeroberfläche der Shell verwendet werden.

  • Der Visual Basic- oder C#-Code hinter der XAML, der das Verhalten der Steuerelemente implementiert und mit der LightSwitch-Laufzeit interagiert.

Das Erstellen einer Shellerweiterung umfasst die folgenden Aufgaben:

  • Erstellen eines Shellerweiterungsprojekts

  • Hinzufügen von Verweisen zu Namespaces

  • Erstellen des MEF-Segments

  • Definieren der Shell

  • Implementieren der Shell

  • Angeben eines Anzeigenamens und einer Beschreibung

  • Testen der Shellerweiterung

Vorbereitungsmaßnahmen

  • Visual Studio 2013 Professional

  • Visual Studio 2013 SDK

  • LightSwitch Extensibility Toolkit für Visual Studio 2013

Erstellen eines Shellerweiterungsprojekts

Der erste Schritt besteht darin, ein Projekt zu erstellen und eine LightSwitch-Shellvorlage hinzuzufügen.

So erstellen Sie ein Erweiterungsprojekt

  1. Wählen Sie in der Visual Studio-Menüleiste Datei, Neu und Projekt aus.

  2. Erweitern Sie im Dialogfeld Neues Projekt den Knoten Visual Basic oder Visual C#, erweitern Sie den Knoten LightSwitch, wählen Sie dann den Knoten Erweiterungen und anschließend die Vorlage LightSwitch-Erweiterungsbibliothek aus.

  3. Geben Sie im Feld Name den Namen ShellExtension für die Erweiterungsbibliothek ein.

  4. Wählen Sie die Schaltfläche OK, um eine Projektmappe zu erstellen, die die sieben Projekte enthält, die für die Erweiterung erforderlich sind.

So wählen Sie einen Erweiterungstyp aus

  1. Wählen Sie im Projektmappen-Explorer das Projekt ShellExtension.Lspkg aus.

  2. Wählen Sie in der Menüleiste Projekt, Neues Element hinzufügen aus.

  3. Wählen Sie im Dialogfeld Neues Element hinzufügen den Eintrag Shell aus.

  4. Geben Sie im Feld Name den Namen ShellSample für die Erweiterung ein.

  5. Klicken Sie auf die Schaltfläche OK. Dateien werden für mehrere Projekte in der Projektmappe hinzugefügt.

Hinzufügen von Verweisen

Die Shellerweiterung muss auf einige Namespaces verweisen, die nicht Bestandteil der Standardvorlage sind.

So fügen Sie Verweise hinzu

  1. Öffnen Sie im Projektmappen-Explorer das Kontextmenü für das ShellExtension.Client-Projekt, und wählen Sie Verweis hinzufügen aus.

  2. Fügen Sie im Dialogfeld Verweis hinzufügen einen Verweis auf System.Windows.Controls.dll hinzu.

  3. Fügen Sie im Dialogfeld Verweis hinzufügen einen Verweis auf Microsoft.LightSwitch.ExportProvider.dll hinzu.

    Die Assembly befindet sich im Ordner PrivateAssembly unter dem Ordner für die Visual Studio-IDE.

Erstellen des MEF-Segments

Um die Implementierung einer Shell für das MEF (Managed Extensibility Framework) verfügbar zu machen, muss eine Klasse erstellt werden, mit der die IShell-Schnittstelle implementiert wird. Zudem sind die erforderlichen Attributausgestaltungen bereitzustellen. Es gibt zwei solche Attribute, die erforderlich sind: Export und Shell. Über das Attribut Export wird dem MEF mitgeteilt, welcher Vertrag von der Klasse implementiert wird. Das Attribut Shell hingegen enthält die Metadaten, mit denen die Implementierung Ihrer Shell von anderen Implementierungen unterschieden wird. Die Implementierung wird über die Projektvorlage hinzugefügt. Sie befindet sich im Ordner Präsentation, Shells, Komponenten im ShellExtension.Client-Projekt. Das folgende Beispiel zeigt die Implementierung.

<Export(GetType(IShell))>
    <Shell(ShellSample.ShellId)>
    Friend Class ShellSample
        Implements IShell
[Export(typeof(IShell))]
    [Shell(ShellSample.ShellId)]
    internal class ShellSample : IShell
    {
     ...
    }

Die im Shell-Attribut angegebenen Daten fungieren als Bezeichner der Shell. Der Wert muss folgendem Format entsprechen: <Module Name>:<Shell Name>. Der Name des Moduls wird in der module.lsml-Datei angegeben, die das Modul beschreibt. Diese Datei und der Modulname werden durch die Projektvorlage generiert. Der Name der Shell wird in der ProjectName.lsml-Datei angegeben, die die Shell beschreibt. Die IShell-Schnittstelle besitzt zwei Methoden: eine gibt den Namen der Shell zurück, der dem im Shell-Attribut angegebenen Wert entspricht, die andere gibt einen URI (Uniform Resource Locator) an die XAML zurück, der eine eingebettete Ressource in der erstellten Assembly ist.

Definieren der Shell

Beim Entwickeln einer Shellerweiterung für LightSwitch haben Sie Freiheit, jedes Steuerelement zu erstellen und zu verwenden, mit dem Sie die gewünschte Darstellung erzielen können. Der Inhalt einer LightSwitch-Anwendung besteht aus einer Reihe bekannter Segmente. Die folgende Tabelle zeigt die definierten Segmente einer LightSwitch-Anwendung an.

Segment

ViewModel-Element

Beschreibung

Navigation

NavigationViewModel

Bietet Zugriff auf den Bereich Navigation, der zum Öffnen von Bildschirmen verwendet wird.

Befehle

CommandsViewModel

Bietet Zugriff auf den Bereich Befehlsleiste, der zur Anzeige von Schaltflächen oder anderen Befehlen verwendet wird.

Aktive Bildschirme

ActiveScreensViewModel

Bietet Zugriff auf den Bereich Bildschirm, der für die Anzeige von Bildschirmen verwendet wird.

Aktueller Benutzer

CurrentUserViewModel

Ermöglicht das Anzeigen von Informationen über den aktuell angemeldeten Benutzer.

Logo

LogoViewModel

Ermöglicht die Anzeige eines Bilds, das in der Logo-Eigenschaft angegeben ist.

Bildschirmvalidierung

ValidationViewModel

Bietet Zugriff auf die Benutzeroberfläche für die Validierung.

Für jedes dieser Segmente stellt LightSwitch ein ViewModel bereit, an das die Steuerelemente gebunden werden können. LightSwitch enthält eine Methode, die eine einfache Bindung an diese ViewModels ermöglicht: ComponentViewModelService. Ist dieser Dienst als Attribut für ein Steuerelement angegeben, sucht er mit dem MEF nach dem angegebenen ViewModel, instanziiert es und legt es als Datenkontext für das Steuerelement fest. Das folgende Codebeispiel zeigt, wie für ein Listenfeld der Datenkontext auf das ViewModel für Befehle festgelegt wird.

<ListBox x:Name="CommandPanel" Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" Background="{StaticResource RibbonBackgroundBrush}"
                 ShellHelpers:ComponentViewModelService.ViewModelName="Default.CommandsViewModel"
                 ItemsSource="{Binding ShellCommands}">
        ...
        </ListBox>

So definieren Sie die Shell

  1. Wählen Sie im Projektmappen-Explorer den Ordner Präsentation, Shells des ShellExtension.Client-Projekts, und öffnen Sie dann die Datei ShellSample.xaml.

  2. Ersetzen Sie den Inhalt durch Folgendes.

    <UserControl x:Class="ShellExtension.Presentation.Shells.ShellSample"
        xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:windows="clr-namespace:System.Windows;assembly=System.Windows.Controls"
        xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
        xmlns:ShellHelpers="clr-namespace:Microsoft.LightSwitch.Runtime.Shell.Helpers;assembly=Microsoft.LightSwitch.Client"
        xmlns:local="clr-namespace:ShellExtension.Presentation.Shells">
    
        <UserControl.Resources>
            <ResourceDictionary>
                <ResourceDictionary.MergedDictionaries>
                    <ResourceDictionary Source="/ShellExtension.Client;component/Presentation/Shells/TextBlockStyle.xaml" />
                </ResourceDictionary.MergedDictionaries>
            </ResourceDictionary>
    
            <!-- Convert the boolean value indicating whether or not the workspace is dirty to a Visibility value. -->
            <local:WorkspaceDirtyConverter x:Key="WorkspaceDirtyConverter" />
    
            <!-- Convert the boolean value indicating whether or not the screen has errors to a Visibility value. -->
            <local:ScreenHasErrorsConverter x:Key="ScreenHasErrorsConverter" />
    
            <!-- Convert the enumeration of errors into a single string. -->
            <local:ScreenResultsConverter x:Key="ScreenResultsConverter" />
    
            <!-- Convert the current user to a "default" value when authentication is not enabled. -->
            <local:CurrentUserConverter x:Key="CurrentUserConverter" />
    
            <!-- Template that is used for the header of each tab item: -->
            <DataTemplate x:Key="TabItemHeaderTemplate">
                <Border BorderBrush="{StaticResource ScreenTabBorderBrush}">
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Style="{StaticResource TextBlockFontsStyle}" Text="{Binding DisplayName}" Foreground="{StaticResource ScreenTabTextBrush}" />
                        <TextBlock Style="{StaticResource TextBlockFontsStyle}" Text="*" 
                                   Visibility="{Binding IsDirty, Converter={StaticResource WorkspaceDirtyConverter}}" 
                                   Margin="5, 0, 5, 0" />
                        <TextBlock Style="{StaticResource TextBlockFontsStyle}" Text="!" 
                                   Visibility="{Binding ValidationResults.HasErrors, Converter={StaticResource ScreenHasErrorsConverter}}" 
                                   Margin="5, 0, 5, 0" Foreground="Red" FontWeight="Bold">
                            <ToolTipService.ToolTip>
                                <ToolTip Content="{Binding ValidationResults, Converter={StaticResource ScreenResultsConverter}}" />
                            </ToolTipService.ToolTip>
                        </TextBlock>
                        <Button Height="16"
                                Width="16"
                                Padding="0"
                                Margin="5, 0, 0, 0"
                                Click="OnClickTabItemClose">X</Button>
                    </StackPanel>
                </Border>
            </DataTemplate>
        </UserControl.Resources>
    
        <Grid x:Name="LayoutRoot" Background="{StaticResource NavShellBackgroundBrush}">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="5*" />
                <RowDefinition Height="*" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
    
            <!-- The command panel is a horizontally oriented list box whose data context is set to the  -->
            <!-- CommandsViewModel.  The ItemsSource of this list box is data bound to the ShellCommands -->
            <!-- property.  This results in each item being bound to an instance of an IShellCommand.    -->
            <!--                                                                                         -->
            <!-- The attribute 'ShellHelpers:ComponentViewModelService.ViewModelName' is the manner by   -->
            <!-- which a control specifies the view model that is to be set as its data context.  In     -->
            <!-- case, the view model is identified by the name 'Default.CommandsViewModel'.             -->
            <ListBox x:Name="CommandPanel" Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" Background="{StaticResource RibbonBackgroundBrush}"
                            ShellHelpers:ComponentViewModelService.ViewModelName="Default.CommandsViewModel"
                            ItemsSource="{Binding ShellCommands}">
    
                <ListBox.ItemsPanel>
                    <ItemsPanelTemplate>
                        <StackPanel Orientation="Horizontal" />
                    </ItemsPanelTemplate>
                </ListBox.ItemsPanel>
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <!-- Each item in the list box will be a button whose content is the following:         -->
                        <!--    1.  An image, which is bound to the Image property of the IShellCommand         -->
                        <!--    2.  A text block whose text is bound to the DisplayName of the IShellCommand   -->
                        <StackPanel Orientation="Horizontal">
                            <!-- The button be enabled or disabled according to the IsEnabled property of  the -->
                            <!-- IShellCommand.  The handler for the click event will execute the command.  -->
                            <Button Click="GeneralCommandHandler"
                                    IsEnabled="{Binding IsEnabled}"
                                    Style="{x:Null}"
                                    Background="{StaticResource ButtonBackgroundBrush}"
                                    Margin="1">
    
                                <Grid>
                                    <Grid.RowDefinitions>
                                        <RowDefinition Height="32" />
                                        <RowDefinition MinHeight="24" Height="*"/>
                                    </Grid.RowDefinitions>
                                    <Image Grid.Row="0"
                                           Source="{Binding Image}"
                                           Width="32"
                                           Height="32"
                                           Stretch="UniformToFill"
                                           Margin="0"
                                           VerticalAlignment="Top"
                                           HorizontalAlignment="Center" />
                                    <TextBlock Grid.Row="1"
                                               Text="{Binding DisplayName}"
                                               TextAlignment="Center"
                                               TextWrapping="Wrap"
                                               Style="{StaticResource TextBlockFontsStyle}"
                                               MaxWidth="64" />
                                </Grid>
                            </Button>
                        </StackPanel>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
    
            <!-- Navigation view is a simple tree view whose ItemsSource property is bound to -->
            <!-- the collection returned from the NavigationItems property of the Navigation  -->
            <!-- view model.                                                                  -->
            <controls:TreeView x:Name="ScreenTree" Grid.Column="0" Grid.Row="1" Grid.RowSpan="2"
                      Background="{StaticResource NavShellBackgroundBrush}"
                      ShellHelpers:ComponentViewModelService.ViewModelName="Default.NavigationViewModel"
                      ItemsSource="{Binding NavigationItems}"
                      Loaded="OnTreeViewLoaded">
                <controls:TreeView.ItemTemplate>
                    <!-- Each navigation item may have children, so set up the binding to the -->
                    <!-- Children property of the INavigationGroup                            -->
                    <windows:HierarchicalDataTemplate ItemsSource="{Binding Children}">
                        <!-- Each item in the TreeView is a TextBlock whose text value is bound to the DisplayName property of the INavigationItem -->
                        <TextBlock Style="{StaticResource TextBlockFontsStyle}" 
                                   Text="{Binding DisplayName}" 
                                   Foreground="{StaticResource NormalFontBrush}" 
                                   MouseLeftButtonDown="NavigationItemLeftButtonDown" />
                    </windows:HierarchicalDataTemplate>
                </controls:TreeView.ItemTemplate>
            </controls:TreeView>
    
            <controls:GridSplitter Grid.Column="0"
                              Grid.Row="1"
                              Grid.RowSpan="2"
                              Style="{x:Null}"
                              Width="5"
                              Name="gridSplitter1"
                              Background="Transparent"
                              HorizontalAlignment="Right"
                              VerticalAlignment="Stretch" />
    
            <!-- Each screen will be displayed in a tab in a tab control.  The individual TabItem -->
            <!-- controls are created in code.                                                    -->
            <controls:TabControl x:Name="ScreenArea"
                                 Grid.Column="1"
                                 Grid.Row="1"
                                 Grid.RowSpan="2"
                                 Background="{StaticResource NavShellBackgroundBrush}"
                                 SelectionChanged="OnTabItemSelectionChanged">
            </controls:TabControl>
    
            <!-- The name of the current user is displayed in the lower-left corner of the shell. -->
            <Grid Grid.Column="0" Grid.Row="3" Grid.ColumnSpan="2">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition Width="Auto" />
                </Grid.ColumnDefinitions>
    
                <TextBlock Grid.Column="0" Style="{StaticResource TextBlockFontsStyle}" Text="Current User: " Foreground="{StaticResource NormalFontBrush}"/>
    
                <!-- This TextBlock has its data context set to the CurrentUserViewModel, from which the -->
                <!-- CurrentUserDisplayName property is used to provide the name of the user displayed.  -->
                <TextBlock Grid.Column="1"
                           Style="{StaticResource TextBlockFontsStyle}"
                           ShellHelpers:ComponentViewModelService.ViewModelName="Default.CurrentUserViewModel"
                           Text="{Binding CurrentUserDisplayName, Converter={StaticResource CurrentUserConverter}}"
                           Foreground="{StaticResource NormalFontBrush}"/>
            </Grid>
        </Grid>
    </UserControl>
    

    Dies enthält die vollständige Codeauflistung für die ShellSample.xaml-Datei, die verschiedenen Abschnitte werden in späteren Schritten erklärt. Sie können alle Fehler bezüglich fehlender Typen ignorieren, diese werden ebenfalls später hinzugefügt.

  3. Öffnen Sie im Projektmappen-Explorer das Kontextmenü für den Knoten Präsentation, Shells im ShellExtension.Client-Projekt, und wählen Sie Neues Element hinzufügen aus.

  4. Erweitern Sie im Dialogfeld Neues Element hinzufügen den Knoten Silverlight, und wählen Sie Silverlight-Ressourcenwörterbuch aus.

  5. Geben Sie im Textfeld Name die Zeichenfolge TextBlockStyle ein, und klicken Sie dann auf die Schaltfläche Hinzufügen.

  6. Ersetzen Sie den vorhandenen XAML-Code durch folgenden Code, um ResourceDictionary zu definieren.

    <ResourceDictionary
        xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Microsoft.LightSwitch.Presentation.Framework.Helpers;assembly=Microsoft.LightSwitch.Client">
    
        <Style x:Key="TextBlockFontsStyle" TargetType="TextBlock">
            <Setter Property="FontFamily" Value="{StaticResource NormalFontFamily}" />
            <Setter Property="FontSize" Value="{StaticResource NormalFontSize}" />
            <Setter Property="FontWeight" Value="{StaticResource NormalFontWeight}" />
            <Setter Property="FontStyle" Value="{StaticResource NormalFontStyle}" />
        </Style>
    
    </ResourceDictionary>
    

    Auf das Ressourcenwörterbuch wird durch den UserControl-XAML-Code verwiesen, und es gibt den Stil an, der für alle TextBlock-Steuerelemente übernommen wird.

Die XAML für die Shell verweist auf einige Wertkonverter, die Sie nachfolgend definieren.

So definieren Sie Wertkonverter

  1. Öffnen Sie im Projektmappen-Explorer das Kontextmenü für den Knoten Präsentation, Shells im ShellExtension.Client-Projekt, und wählen Sie Neues Element hinzufügen aus.

  2. Erweitern Sie im Dialogfeld Neues Element hinzufügen den Knoten Code, und wählen Sie Klasse aus.

  3. Geben Sie im Textfeld Name die Zeichenfolge Converters ein, und klicken Sie dann auf die Schaltfläche Hinzufügen.

  4. Ersetzen Sie den Inhalt durch den folgenden Code.

    Imports System
    Imports System.Collections.Generic
    Imports System.Globalization
    Imports System.Linq
    Imports System.Text
    Imports System.Windows
    Imports System.Windows.Data
    Imports System.Windows.Media
    Imports Microsoft.LightSwitch
    Imports Microsoft.LightSwitch.Details
    Imports Microsoft.LightSwitch.Client
    Imports Microsoft.LightSwitch.Details.Client
    
    Namespace Presentation.Shells
    
        Public Class WorkspaceDirtyConverter
            Implements IValueConverter
    
            Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
                Return If(CType(value, Boolean), Visibility.Visible, Visibility.Collapsed)
            End Function
    
            Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
                Throw New NotSupportedException()
            End Function
    
        End Class
    
        Public Class ScreenHasErrorsConverter
            Implements IValueConverter
    
            Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
                Return If(CType(value, Boolean), Visibility.Visible, Visibility.Collapsed)
            End Function
    
            Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
                Throw New NotSupportedException()
            End Function
    
        End Class
    
        Public Class ScreenResultsConverter
            Implements IValueConverter
    
            Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
                Dim results As ValidationResults = value
                Dim sb As StringBuilder = New StringBuilder()
    
                For Each result As ValidationResult In results.Errors
                    sb.Append(String.Format("Errors: {0}", result.Message))
                Next
    
                Return sb.ToString()
            End Function
    
            Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
                Throw New NotSupportedException()
            End Function
    
        End Class
    
        Public Class CurrentUserConverter
            Implements IValueConverter
    
            Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
                Dim currentUser As String = value
    
                If currentUser Is Nothing OrElse currentUser.Length = 0 Then
                    Return "Authentication is not enabled."
                End If
    
                Return currentUser
            End Function
    
            Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
                Throw New NotSupportedException()
            End Function
    
        End Class
    
    End Namespace
    
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;
    using System.Text;
    using System.Windows;
    using System.Windows.Data;
    using System.Windows.Media;
    
    namespace ShellExtension.Presentation.Shells
    {
        using Microsoft.LightSwitch;
        using Microsoft.LightSwitch.Details;
        using Microsoft.LightSwitch.Client;
        using Microsoft.LightSwitch.Details.Client;
    
        public class WorkspaceDirtyConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            {
                return (bool)value ? Visibility.Visible : Visibility.Collapsed;
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
            {
                throw new NotSupportedException();
            }
        }
    
        public class ScreenHasErrorsConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            {
                return (bool)value ? Visibility.Visible : Visibility.Collapsed;
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
            {
                throw new NotSupportedException();
            }
        }
    
        public class ScreenResultsConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            {
                ValidationResults   results = (ValidationResults)value;
                StringBuilder       sb      = new StringBuilder();
    
                foreach(ValidationResult result in results.Errors)
                    sb.AppendLine(String.Format("Error: {0}", result.Message));
    
                return sb.ToString();
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
            {
                throw new NotSupportedException();
            }
        }
    
        public class CurrentUserConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            {
                string  currentUser = (string)value;
    
                if ( (null == currentUser) || (0 == currentUser.Length) )
                    return "Authentication is not enabled.";
    
                return currentUser;
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
            {
                throw new NotSupportedException();
            }
        }
    }
    

Implementieren der Shell

Beim Schreiben einer Shell müssen zahlreiche Funktionen implementiert und mehrere Systeme in der LightSwitch-Laufzeit verwendet werden. Der erste Schritt besteht darin, die standardmäßige Implementierung der Shell in der Code-Behind-Datei zu aktualisieren.

Hh290138.collapse_all(de-de,VS.140).gifAktualisieren der standardmäßigen Shellimplementierung

Die Shellvorlage stellt einen Ausgangspunkt für das Erstellen der Shellerweiterung bereit. Die Basisimplementierung wird erweitert, um die Funktionalität zu definieren, die Ihre Shell bereitstellen soll.

So aktualisieren Sie die Shellimplementierung

  1. Wählen Sie im Projektmappen-Explorer im ShellExtension.Client-Projekt den Ordner Präsentation, Shells aus, und öffnen Sie dann die ShellSample.xaml.vb-Datei bzw. die ShellSample.xaml.cs-Datei.

  2. Ersetzen Sie die Imports-Anweisungen bzw. die using-Anweisungen durch Folgendes.

    Imports System
    Imports System.Collections.Generic
    Imports System.Collections.Specialized
    Imports System.Linq
    Imports System.Net
    Imports System.Windows
    Imports System.Windows.Controls
    Imports System.Windows.Data
    Imports System.Windows.Documents
    Imports System.Windows.Input
    Imports System.Windows.Media
    Imports System.Windows.Media.Animation
    Imports System.Windows.Media.Imaging
    Imports System.Windows.Shapes
    Imports System.Windows.Threading
    Imports Microsoft.VisualStudio.ExtensibilityHosting
    Imports Microsoft.LightSwitch.Sdk.Proxy
    Imports Microsoft.LightSwitch.Runtime.Shell
    Imports Microsoft.LightSwitch.Runtime.Shell.View
    Imports Microsoft.LightSwitch.Runtime.Shell.ViewModels.Commands
    Imports Microsoft.LightSwitch.Runtime.Shell.ViewModels.Navigation
    Imports Microsoft.LightSwitch.Runtime.Shell.ViewModels.Notifications
    Imports Microsoft.LightSwitch.BaseServices.Notifications
    Imports Microsoft.LightSwitch.Client
    Imports Microsoft.LightSwitch.Framework.Client
    Imports Microsoft.LightSwitch.Threading
    Imports Microsoft.LightSwitch.Details
    
    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Linq;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Media.Imaging;
    using System.Windows.Shapes;
    using System.Windows.Threading;
    using Microsoft.VisualStudio.ExtensibilityHosting;
    using Microsoft.LightSwitch.Sdk.Proxy;
    using Microsoft.LightSwitch.Runtime.Shell;
    using Microsoft.LightSwitch.Runtime.Shell.View;
    using Microsoft.LightSwitch.Runtime.Shell.ViewModels.Commands;
    using Microsoft.LightSwitch.Runtime.Shell.ViewModels.Navigation;
    using Microsoft.LightSwitch.Runtime.Shell.ViewModels.Notifications;
    using Microsoft.LightSwitch.BaseServices.Notifications;
    using Microsoft.LightSwitch.Client;
    using Microsoft.LightSwitch.Framework.Client;
    using Microsoft.LightSwitch.Threading;
    using Microsoft.LightSwitch.Details;
    
  3. Ersetzen Sie den Code im Presentation.Shells-Namespace (Visual Basic) bzw. im ShellExtension.Presentation.Shells-Namespace (C#) durch den folgenden Code.

    Partial Public Class ShellSample
            Inherits UserControl
    
            Private serviceProxyCache As IServiceProxy
            Private weakHelperObjects As List(Of Object) = New List(Of Object)()
            Private doubleClickTimer As DispatcherTimer
    
            Public Sub New()
                InitializeComponent()
    
                ' Use the notification service, found on the service proxy, to subscribe to the ScreenOpened,
                ' ScreenClosed, and ScreenReloaded notifications.
                Me.ServiceProxy.NotificationService.Subscribe(GetType(ScreenOpenedNotification), AddressOf Me.OnScreenOpened)
                Me.ServiceProxy.NotificationService.Subscribe(GetType(ScreenClosedNotification), AddressOf Me.OnScreenClosed)
                Me.ServiceProxy.NotificationService.Subscribe(GetType(ScreenReloadedNotification), AddressOf Me.OnScreenRefreshed)
    
                ' Sign up for the Closing event on the user settings service so we can committ any settings changes.
                AddHandler Me.ServiceProxy.UserSettingsService.Closing, AddressOf Me.OnSettingsServiceClosing
    
                ' Read in the saved settings from the user settings service.  This shell saves the width of
                ' the two columns that are separated by a grid splitter.
                Dim width1 As Double = Me.ServiceProxy.UserSettingsService.GetSetting(Of Double)("RootColumn1_Size")
                Dim width2 As Double = Me.ServiceProxy.UserSettingsService.GetSetting(Of Double)("RootColumn2_Size")
    
                ' If the settings were successfully retrieved, then set the column widths accordingly.
                If width1 <> 0 Then
                    Me.LayoutRoot.ColumnDefinitions(0).Width = New GridLength(width1, GridUnitType.Star)
                End If
    
                If width2 <> 0 Then
                    Me.LayoutRoot.ColumnDefinitions(1).Width = New GridLength(width2, GridUnitType.Star)
                End If
    
                ' Initialize the double-click timer (which is used for managing double clicks on an item in the tree view).
                Me.doubleClickTimer = New DispatcherTimer()
                Me.doubleClickTimer.Interval = New TimeSpan(0, 0, 0, 0, 200)
                AddHandler Me.doubleClickTimer.Tick, AddressOf Me.OnDoubleClickTimerTick
            End Sub
    
            Public Sub OnSettingsServiceClosing(sender As Object, e As EventArgs)
                ' This function will get called when the settings service is closing, which happens
                ' when the application is shut down.  In response to that event we will save the
                ' current widths of the two columns.
                Me.ServiceProxy.UserSettingsService.SetSetting("RootColumn1_Size", Me.LayoutRoot.ColumnDefinitions(0).ActualWidth)
                Me.ServiceProxy.UserSettingsService.SetSetting("RootColumn2_Size", Me.LayoutRoot.ColumnDefinitions(1).ActualWidth)
            End Sub
    
            Public Sub OnScreenOpened(n As Notification)
                ' This method is called when a screen has been opened by the runtime.  In response to
                ' this, we need to create a tab item and set its content to be the UI for the newly
                ' opened screen.
                Dim screenOpenedNotification As ScreenOpenedNotification = n
                Dim screenObject As IScreenObject = screenOpenedNotification.Screen
                Dim view As IScreenView = Me.ServiceProxy.ScreenViewService.GetScreenView(screenObject)
    
                ' Create a tab item and bind its header to the display name of the screen.
                Dim ti As TabItem = New TabItem()
                Dim template As DataTemplate = Me.Resources("TabItemHeaderTemplate")
                Dim element As UIElement = template.LoadContent()
    
                ' The IScreenObject does not contain properties indicating if the screen has
                ' changes or validation errors.  As such, we have created a thin wrapper around the
                ' screen object that does expose this functionality.  This wrapper, a class called
                ' MyScreenObject, is what we'll use as the data context for the tab item.
                ti.DataContext = New MyScreenObject(screenObject)
                ti.Header = element
                ti.HeaderTemplate = template
                ti.Content = view.RootUI
    
                ' Add the tab item to the tab control.
                Me.ScreenArea.Items.Add(ti)
                Me.ScreenArea.SelectedItem = ti
    
                ' Set the currently active screen in the active screens view model.
                Me.ServiceProxy.ActiveScreensViewModel.Current = screenObject
            End Sub
    
            Public Sub OnScreenClosed(n As Notification)
                ' A screen has been closed and therefore removed from the application's
                ' collection of active screens.  In response to this, we need to do
                ' two things:
                '  1.  Remove the tab item that was displaying this screen.
                '  2.  Set the "current" screen to the screen that will be displayed
                '      in the tab item that will be made active.
                Dim screenClosedNotification As ScreenClosedNotification = n
                Dim screenObject As IScreenObject = screenClosedNotification.Screen
    
                For Each ti As TabItem In Me.ScreenArea.Items
                    ' We need to get the real IScreenObject from the instance of the MyScreenObject.
                    Dim realScreenObject As IScreenObject = CType(ti.DataContext, MyScreenObject).RealScreenObject
    
                    If realScreenObject Is screenObject Then
                        Me.ScreenArea.Items.Remove(ti)
                        Exit For
                    End If
                Next
    
                ' If there are any tab items left, set the current tab to the last one in the list
                ' AND set the current screen to be the screen contained within that tab item.
                Dim count As Integer = Me.ScreenArea.Items.Count
    
                If count > 0 Then
                    Dim ti As TabItem = Me.ScreenArea.Items(count - 1)
    
                    Me.ScreenArea.SelectedItem = ti
                    Me.ServiceProxy.ActiveScreensViewModel.Current = CType(ti.DataContext, MyScreenObject).RealScreenObject
                End If
            End Sub
    
            Public Sub OnScreenRefreshed(n As Notification)
                ' When a screen is refreshed, the runtime actually creates a new IScreenObject
                ' for it and discards the old one.  So in response to this notification what
                ' we need to do is replace the data context for the tab item that contains
                ' this screen with a wrapper (MyScreenObject) for the new IScreenObject instance.
                Dim srn As ScreenReloadedNotification = n
    
                For Each ti As TabItem In Me.ScreenArea.Items
    
                    Dim realScreenObject As IScreenObject = CType(ti.DataContext, MyScreenObject).RealScreenObject
    
                    If realScreenObject Is srn.OriginalScreen Then
                        Dim view As IScreenView = Me.ServiceProxy.ScreenViewService.GetScreenView(srn.NewScreen)
    
                        ti.Content = view.RootUI
                        ti.DataContext = New MyScreenObject(srn.NewScreen)
                        Exit For
                    End If
                Next
            End Sub
    
            Private ReadOnly Property ServiceProxy As IServiceProxy
                Get
                    If Me.serviceProxyCache Is Nothing Then
                        Me.serviceProxyCache = VsExportProviderService.GetExportedValue(Of IServiceProxy)()
                    End If
                    Return Me.serviceProxyCache
                End Get
            End Property
    
            Private Sub GeneralCommandHandler(sender As Object, e As RoutedEventArgs)
                ' This function will get called when the user clicks one of the buttons on
                ' the command panel.  The sender is the button whose data context is the
                ' IShellCommand.
                '
                ' In order to execute the command (asynchronously) we simply call the
                ' ExecuteAsync method on the ExecutableObject property of the command.
                Dim command As IShellCommand = CType(sender, Button).DataContext
    
                command.ExecutableObject.ExecuteAsync()
            End Sub
    
            Private Sub OnTreeViewLoaded(sender As Object, e As RoutedEventArgs)
                Me.ScreenTree.Dispatcher.BeginInvoke(
                    Sub()
                        Dim tv As TreeView = sender
    
                        For Each item As Object In tv.Items
                            Dim tvi As TreeViewItem = tv.ItemContainerGenerator.ContainerFromItem(item)
    
                            tvi.IsExpanded = True
                        Next
                    End Sub)
            End Sub
    
            Private Sub OnTabItemSelectionChanged(sender As Object, e As SelectionChangedEventArgs)
                ' When the user selects a tab item, we need to set the "active" screen
                ' in the ActiveScreensView model.  Doing this causes the commands view
                ' model to be udpated to reflect the commands of the current screen.
                If e.AddedItems.Count > 0 Then
                    Dim selectedItem As TabItem = e.AddedItems(0)
    
                    If selectedItem IsNot Nothing Then
                        Dim screenObject As IScreenObject = CType(selectedItem.DataContext, MyScreenObject).RealScreenObject
    
                        Me.ServiceProxy.ActiveScreensViewModel.Current = screenObject
                    End If
                End If
            End Sub
    
            Private Sub OnClickTabItemClose(sender As Object, e As RoutedEventArgs)
                ' When the user closes a tab, we simply need to close the corresponding
                ' screen object.  The only caveat here is that the call to the Close
                ' method needs to happen on the logic thread for the screen.  To do this
                ' we need to use the Dispatcher object for the screen.
                Dim screenObject As IScreenObject = TryCast(CType(sender, Button).DataContext, IScreenObject)
    
                If screenObject IsNot Nothing Then
                    screenObject.Details.Dispatcher.EnsureInvoke(
                        Sub()
                            screenObject.Close(True)
                        End Sub)
                End If
            End Sub
    
            Private Sub NavigationItemLeftButtonDown(sender As Object, e As MouseButtonEventArgs)
                If Me.doubleClickTimer.IsEnabled Then
                    Me.doubleClickTimer.Stop()
    
                    ' If the item clicked on is a screen item, then open the screen.
                    Dim screen As INavigationScreen = TryCast(CType(sender, TextBlock).DataContext, INavigationScreen)
    
                    If screen IsNot Nothing Then
                        screen.ExecutableObject.ExecuteAsync()
                    End If
                Else
                    Me.doubleClickTimer.Start()
                End If
            End Sub
    
            Private Sub OnDoubleClickTimerTick(sender As Object, e As EventArgs)
                Me.doubleClickTimer.Stop()
            End Sub
    
        End Class
    
    public partial class ShellSample : UserControl
        {
            private IServiceProxy   serviceProxy;
            private List<object>    weakHelperObjects   = new List<object>();
            private DispatcherTimer doubleClickTimer;
    
            public ShellSample()
            {   
                this.InitializeComponent();
    
                // Use the notification service, found on the service proxy, to subscribe to the ScreenOpened,
                // ScreenClosed, and ScreenReloaded notifications.
                this.ServiceProxy.NotificationService.Subscribe(typeof(ScreenOpenedNotification), this.OnScreenOpened);
                this.ServiceProxy.NotificationService.Subscribe(typeof(ScreenClosedNotification), this.OnScreenClosed);
                this.ServiceProxy.NotificationService.Subscribe(typeof(ScreenReloadedNotification), this.OnScreenRefreshed);
    
                // Sign up for the Closing event on the user settings service so we can commit any settings changes.
                this.ServiceProxy.UserSettingsService.Closing += this.OnSettingsServiceClosing;
    
                // Read in the saved settings from the user settings service.  This shell saves the width of
                // the two columns that are separated by a grid splitter.
                double  width1 = this.ServiceProxy.UserSettingsService.GetSetting<double>("RootColumn1_Size");
                double  width2 = this.ServiceProxy.UserSettingsService.GetSetting<double>("RootColumn2_Size");
    
                // If the settings were successfully retrieved, then set the column widths accordingly.
                if ( width1 != default(double) )
                    this.LayoutRoot.ColumnDefinitions[0].Width = new GridLength(width1, GridUnitType.Star);
    
                if ( width2 != default(double) )
                    this.LayoutRoot.ColumnDefinitions[1].Width = new GridLength(width2, GridUnitType.Star);
    
                // Initialize the double-click timer, which is used for managing double clicks on an item in the tree view.
                this.doubleClickTimer               = new DispatcherTimer();
                this.doubleClickTimer.Interval      = new TimeSpan(0, 0, 0, 0, 200);
                this.doubleClickTimer.Tick          += new EventHandler(this.OnDoubleClickTimerTick);
            }
    
            public void OnSettingsServiceClosing(object sender, EventArgs e)
            {
                // This function will get called when the settings service is closing, which happens
                // when the application is shut down.  In response to that event we will save the
                // current widths of the two columns.
                this.ServiceProxy.UserSettingsService.SetSetting("RootColumn1_Size", this.LayoutRoot.ColumnDefinitions[0].ActualWidth);
                this.ServiceProxy.UserSettingsService.SetSetting("RootColumn2_Size", this.LayoutRoot.ColumnDefinitions[1].ActualWidth);
            }
    
            public void OnScreenOpened(Notification n)
            {
                // This method is called when a screen has been opened by the run time.  In response to
                // this, we need to create a tab item and set its content to be the UI for the newly
                // opened screen.
                ScreenOpenedNotification    screenOpenedNotification    = (ScreenOpenedNotification)n;
                IScreenObject               screenObject                = screenOpenedNotification.Screen;
                IScreenView                 view                        = this.ServiceProxy.ScreenViewService.GetScreenView(screenObject);
    
                // Create a tab item and bind its header to the display name of the screen.
                TabItem         ti          = new TabItem();
                DataTemplate    template    = (DataTemplate)this.Resources["TabItemHeaderTemplate"];
                UIElement       element     = (UIElement)template.LoadContent();
    
                // The IScreenObject does not contain properties indicating if the screen has
                // changes or validation errors.  As such, we have created a thin wrapper around the
                // screen object that does expose this functionality.  This wrapper, a class called
                // MyScreenObject, is what we'll use as the data context for the tab item.
                ti.DataContext      = new MyScreenObject(screenObject);
                ti.Header           = element;
                ti.HeaderTemplate   = template;
                ti.Content          = view.RootUI;
    
                // Add the tab item to the tab control.
                this.ScreenArea.Items.Add(ti);
                this.ScreenArea.SelectedItem = ti;
    
                // Set the currently active screen in the active screens view model.
                this.ServiceProxy.ActiveScreensViewModel.Current = screenObject;
            }
    
            public void OnScreenClosed(Notification n)
            {
                // A screen has been closed and therefore removed from the application's
                // collection of active screens.  In response to this, we need to do
                // two things:
                //  1.  Remove the tab item that was displaying this screen.
                //  2.  Set the "current" screen to the screen that will be displayed
                //      in the tab item that will be made active.
                ScreenClosedNotification    screenClosedNotification    = (ScreenClosedNotification)n;
                IScreenObject               screenObject                = screenClosedNotification.Screen;
    
                foreach(TabItem ti in this.ScreenArea.Items)
                {
                    // We need to get the real IScreenObject from the instance of the MyScreenObject.
                    IScreenObject   realScreenObject = ((MyScreenObject)ti.DataContext).RealScreenObject;
    
                    if ( realScreenObject == screenObject )
                    {
                        this.ScreenArea.Items.Remove(ti);
                        break;
                    }
                }
    
                // If there are any tab items left, set the current tab to the last one in the list
                // AND set the current screen to be the screen contained within that tab item.
                int count = this.ScreenArea.Items.Count;
    
                if ( count > 0 )
                {
                    TabItem ti = (TabItem)this.ScreenArea.Items[count - 1];
    
                    this.ScreenArea.SelectedItem = ti;
                    this.ServiceProxy.ActiveScreensViewModel.Current = ((MyScreenObject)(ti.DataContext)).RealScreenObject;
                }
            }
    
            public void OnScreenRefreshed(Notification n)
            {
                // When a screen is refreshed, the run time actually creates a new IScreenObject
                // for it and discards the old one.  So in response to this notification, 
                // replace the data context for the tab item that contains
                // this screen with a wrapper (MyScreenObject) for the new IScreenObject instance.
                ScreenReloadedNotification  srn = (ScreenReloadedNotification)n;
    
                foreach(TabItem ti in this.ScreenArea.Items)
                {
                    IScreenObject   realScreenObject = ((MyScreenObject)ti.DataContext).RealScreenObject;
    
                    if ( realScreenObject == srn.OriginalScreen )
                    {
                        IScreenView view = this.ServiceProxy.ScreenViewService.GetScreenView(srn.NewScreen);
    
                        ti.Content      = view.RootUI;
                        ti.DataContext  = new MyScreenObject(srn.NewScreen);
                        break;
                    }
                }
            }
    
            private IServiceProxy ServiceProxy
            {
                get
                {
                    // Get the service proxy that provides access to the needed LightSwitch services.
                    if ( null == this.serviceProxy )
                        this.serviceProxy = VsExportProviderService.GetExportedValue<IServiceProxy>();
    
                    return this.serviceProxy;
                }
            }
    
            private void GeneralCommandHandler(object sender, RoutedEventArgs e)
            {
                // This function will get called when the user clicks one of the buttons on
                // the command panel.  The sender is the button whose data context is the
                // IShellCommand.
                //
                // In order to execute the command (asynchronously) we simply call the
                // ExecuteAsync method on the ExecutableObject property of the command.
                IShellCommand   command = (IShellCommand)((Button)sender).DataContext;
    
                command.ExecutableObject.ExecuteAsync();
            }
    
            private void OnTreeViewLoaded(object sender, RoutedEventArgs e)
            {
                this.ScreenTree.Dispatcher.BeginInvoke(() =>
                {
                    TreeView    tv = (TreeView)sender;
    
                    foreach(object item in tv.Items)
                    {
                        TreeViewItem    tvi = tv.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
    
                        tvi.IsExpanded = true;
                    }
                });
            }
    
            private void OnTabItemSelectionChanged(object sender, SelectionChangedEventArgs e)
            {
                // When the user selects a tab item, set the "active" screen
                // in the ActiveScreensView model.  Doing this causes the commands view
                // model to be udpated to reflect the commands of the current screen.
                if ( e.AddedItems.Count > 0 )
                {
                    TabItem selectedItem = (TabItem)e.AddedItems[0];
    
                    if ( null != selectedItem )
                    {
                        IScreenObject   screenObject = ((MyScreenObject)selectedItem.DataContext).RealScreenObject;
    
                        this.ServiceProxy.ActiveScreensViewModel.Current = screenObject;
                    }
                }
            }
    
            private void OnClickTabItemClose(object sender, RoutedEventArgs e)
            {
                // When the user closes a tab, we simply need to close the corresponding
                // screen object.  The only caveat here is that the call to the Close
                // method needs to happen on the logic thread for the screen.  To do this, 
                // use the Dispatcher object for the screen.
                IScreenObject   screenObject = ((Button)sender).DataContext as IScreenObject;
    
                if ( null != screenObject )
                {
                    screenObject.Details.Dispatcher.EnsureInvoke(() =>
                    {
                        screenObject.Close(true);
                    });
                }
            }
    
            private void NavigationItemLeftButtonDown(object sender, MouseButtonEventArgs e)
            {
                if ( this.doubleClickTimer.IsEnabled )
                {
                    this.doubleClickTimer.Stop();
    
                    // If the item clicked on is a screen item, then open the screen.
                    INavigationScreen   screen = ((TextBlock)sender).DataContext as INavigationScreen;
    
                    if ( null != screen )
                        screen.ExecutableObject.ExecuteAsync();
                }
                else
                    this.doubleClickTimer.Start();
            }
    
            private void OnDoubleClickTimerTick(object sender, EventArgs e)
            {
                this.doubleClickTimer.Stop();
            }
        }
    

LightSwitch enthält ein Objekt, das die IServiceProxy-Schnittstelle implementiert, die Zugriff auf die erforderlichen LightSwitch-Dienste bietet. Das folgende Codesegment zeigt, wie das IServiceProxy-Objekt vom MEF mithilfe des statischen VsExportProviderService-Elements abgerufen werden kann.

Private ReadOnly Property ServiceProxy As IServiceProxy
            Get
                If Me.serviceProxyCache Is Nothing Then
                    Me.serviceProxyCache = VsExportProviderService.GetExportedValue(Of IServiceProxy)()
                End If
                Return Me.serviceProxyCache
            End Get
        End Property
private IServiceProxy ServiceProxy
        {
            get
            {
                // Get the service proxy, which provides access to the needed LightSwitch services.
                if ( null == this.serviceProxy )
                    this.serviceProxy = VsExportProviderService.GetExportedValue<IServiceProxy>();

                return this.serviceProxy;
            }
        }

Im Konstruktor für das Hauptsteuerelement sind mehrere Benachrichtigungen zu abonnieren, die einige wichtige Hookpunkte für den Workflow der LightSwitch-Laufzeit bereitstellen. Diese Benachrichtigungen sind ScreenOpenedNotification, ScreenClosedNotification und ScreenReloadedNofitication. Sofern Sie möchten, dass Ihre Shell ebenso wie die Beispielshell Einstellungen liest und schreibt, sollten Sie zudem die Registrierung des Closing-Ereignisses auf dem UserSettingsService-Objekt vornehmen. Das folgende Codesegment implementiert den Konstruktor und bindet die Benachrichtigungen ein.

Public Sub New()
            InitializeComponent()

            ' Use the notification service, found on the service proxy, to subscribe to the ScreenOpened,
            ' ScreenClosed, and ScreenReloaded notifications.
            Me.ServiceProxy.NotificationService.Subscribe(GetType(ScreenOpenedNotification), AddressOf Me.OnScreenOpened)
            Me.ServiceProxy.NotificationService.Subscribe(GetType(ScreenClosedNotification), AddressOf Me.OnScreenClosed)
            Me.ServiceProxy.NotificationService.Subscribe(GetType(ScreenReloadedNotification), AddressOf Me.OnScreenRefreshed)

            ' Sign up for the Closing event on the user settings service so we can committ any settings changes.
            AddHandler Me.ServiceProxy.UserSettingsService.Closing, AddressOf Me.OnSettingsServiceClosing

            ' Read in the saved settings from the user settings service.  This shell saves the width of
            ' the two columns that are separated by a grid splitter.
            Dim width1 As Double = Me.ServiceProxy.UserSettingsService.GetSetting(Of Double)("RootColumn1_Size")
            Dim width2 As Double = Me.ServiceProxy.UserSettingsService.GetSetting(Of Double)("RootColumn2_Size")

            ' If the settings were successfully retrieved, then set the column widths accordingly.
            If width1 <> 0 Then
                Me.LayoutRoot.ColumnDefinitions(0).Width = New GridLength(width1, GridUnitType.Star)
            End If

            If width2 <> 0 Then
                Me.LayoutRoot.ColumnDefinitions(1).Width = New GridLength(width2, GridUnitType.Star)
            End If

            ' Initialize the double-click timer (which is used for managing double clicks on an item in the tree view).
            Me.doubleClickTimer = New DispatcherTimer()
            Me.doubleClickTimer.Interval = New TimeSpan(0, 0, 0, 0, 200)
            AddHandler Me.doubleClickTimer.Tick, AddressOf Me.OnDoubleClickTimerTick
        End Sub
public ShellSample()
        {
            this.InitializeComponent();

            // Use the notification service, found on the service proxy, to subscribe to the ScreenOpened,
            // ScreenClosed, and ScreenReloaded notifications.
            this.ServiceProxy.NotificationService.Subscribe(typeof(ScreenOpenedNotification), this.OnScreenOpened);
            this.ServiceProxy.NotificationService.Subscribe(typeof(ScreenClosedNotification), this.OnScreenClosed);
            this.ServiceProxy.NotificationService.Subscribe(typeof(ScreenReloadedNotification), this.OnScreenRefreshed);

            // Sign up for the Closing event on the user settings service so you can commit any settings changes.
            this.ServiceProxy.UserSettingsService.Closing += this.OnSettingsServiceClosing;

            // Read in the saved settings from the user settings service.  This shell saves the width of
            // the two columns that are separated by a grid splitter.
            double width1 = this.ServiceProxy.UserSettingsService.GetSetting<double>("RootColumn1_Size");
            double width2 = this.ServiceProxy.UserSettingsService.GetSetting<double>("RootColumn2_Size");

            // If the settings were successfully retrieved, then set the column widths accordingly.
            if (width1 != default(double))
                this.LayoutRoot.ColumnDefinitions[0].Width = new GridLength(width1, GridUnitType.Star);

            if (width2 != default(double))
                this.LayoutRoot.ColumnDefinitions[1].Width = new GridLength(width2, GridUnitType.Star);

            // Initialize the double-click timer, which is used for managing double clicks on an item in the tree view.
            this.doubleClickTimer = new DispatcherTimer();
            this.doubleClickTimer.Interval = new TimeSpan(0, 0, 0, 0, 200);
            this.doubleClickTimer.Tick += new EventHandler(this.OnDoubleClickTimerTick);
        }

Hh290138.collapse_all(de-de,VS.140).gifSo zeigen Sie verfügbare Bildschirme an

Die Beispielshell verwendet ein standardmäßiges TreeView-Steuerelement von Silverlight, um die Bildschirme in ihren entsprechenden Gruppierungen anzuzeigen. Der folgende Auszug aus der ShellSample.xaml-Datei implementiert den Bereich Navigation.

<!-- Navigation view is a simple tree view whose ItemsSource property is bound to -->
        <!-- the collection returned from the NavigationItems property of the Navigation  -->
        <!-- view model.                                                                  -->
        <controls:TreeView x:Name="ScreenTree" Grid.Column="0" Grid.Row="1" Grid.RowSpan="2"
                  Background="{StaticResource NavShellBackgroundBrush}"
                  ShellHelpers:ComponentViewModelService.ViewModelName="Default.NavigationViewModel"
                  ItemsSource="{Binding NavigationItems}"
                  Loaded="OnTreeViewLoaded">
            <controls:TreeView.ItemTemplate>
                <!-- Each navigation item might have children, so set up the binding to the -->
                <!-- Children property of the INavigationGroup                            -->
                <windows:HierarchicalDataTemplate ItemsSource="{Binding Children}">
                    <!-- Each item in the TreeView is a TextBlock whose text value is bound to the DisplayName property of the INavigationItem. -->
                    <TextBlock Style="{StaticResource TextBlockFontsStyle}" 
                               Text="{Binding DisplayName}" 
                               Foreground="{StaticResource NormalFontBrush}" 
                               MouseLeftButtonDown="NavigationItemLeftButtonDown" />
                </windows:HierarchicalDataTemplate>
            </controls:TreeView.ItemTemplate>
        </controls:TreeView>

Die XAML gibt an, dass der Datenkontext für das TreeView-Steuerelement auf das ViewModel festgelegt wird, das durch den Default.NavigationViewModel-Wert bezeichnet wird, indem dieser als Wert für ComponentViewModelService definiert ist. Die INavigationViewModel-Schnittstelle verfügt über eine Eigenschaft mit dem Namen NavigationItems, die eine Auflistung von INavigationItem-Objekten zurückgibt. Die ItemsSource-Eigenschaft des TreeView-Steuerelements ist an diese Auflistung gebunden.

Jedes INavigationItem-Element kann als INavigationScreen-Objekt fungieren, das einen ausführbaren Bildschirm darstellt. Es kann auch als INavigationGroup-Objekt fungieren, das einen Container für andere INavigationItem-Objekte darstellt. Daher ist die Elementvorlage für das Strukturansicht-Steuerelement an die Children-Eigenschaft gebunden, und jedes untergeordnete Element ist ein einfaches TextBlock-Steuerelement, dessen Text-Eigenschaft auf die DisplayName-Eigenschaft des INavigationItem-Objekts festgelegt ist. Beachten Sie, dass nicht von der Shell ausführbare Bildschirme (beispielsweise parametrisierte Bildschirme oder Bildschirme, auf die der Benutzer keinen Zugriff hat) von NavigationViewModel gefiltert werden.

Hh290138.collapse_all(de-de,VS.140).gifSo öffnen Sie einen Bildschirm

Durch einen Doppelklick des Benutzers auf ein Bildschirmnavigationselement im Bereich Navigation der Beispielshell wird ein Bildschirm auf einer Registerkarte in einem Registerkarten-Steuerelement geöffnet. Die XAML für das TextBlock-Steuerelement, mit dem die einzelnen Navigationselemente angezeigt werden, gibt einen Handler für das MouseLeftButtonDown-Ereignis an. In diesem Handler befindet sich die Logik zum Öffnen eines Bildschirms. Mit dem folgenden Codesegment aus der ShellSample-Klasse wird der Handler hinzugefügt.

Private Sub NavigationItemLeftButtonDown(sender As Object, e As MouseButtonEventArgs)
            If Me.doubleClickTimer.IsEnabled Then
                Me.doubleClickTimer.Stop()

                ' If the item clicked on is a screen item, then open the screen.
                Dim screen As INavigationScreen = TryCast(CType(sender, TextBlock).DataContext, INavigationScreen)

                If screen IsNot Nothing Then
                    screen.ExecutableObject.ExecuteAsync()
                End If
            Else
                Me.doubleClickTimer.Start()
            End If
        End Sub

        Private Sub OnDoubleClickTimerTick(sender As Object, e As EventArgs)
            Me.doubleClickTimer.Stop()
        End Sub
private void NavigationItemLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if ( this.doubleClickTimer.IsEnabled )
            {
                this.doubleClickTimer.Stop();

                // If the item that is clicked is a screen item, then open the screen.
                INavigationScreen   screen = ((TextBlock)sender).DataContext as INavigationScreen;

                if ( null != screen )
                    screen.ExecutableObject.ExecuteAsync();
            }
            else
                this.doubleClickTimer.Start();
        }
private void OnDoubleClickTimerTick(object sender, EventArgs e)
        {
            this.doubleClickTimer.Stop();
        } 

Der Datenkontext des TextBlock-Steuerelements ist eine Instanz der INavigationItem-Schnittstelle. Wenn dieses Objekt erfolgreich in eine Instanz der INavigationScreen-Schnittstelle umgewandelt werden kann, sollte der Bildschirm angezeigt (ausgeführt) werden. Die ExecutableObject-Eigenschaft auf dem INavigationScreen-Objekt enthält eine ExecuteAsync-Methode, die veranlasst, dass der Bildschirm von der LightSwitch-Laufzeit geladen wird. Ist der Bildschirm geladen, wird die Benachrichtigung ScreenOpenedNotification veröffentlicht und der entsprechende Handler aufgerufen.

Das ScreenOpenedNotification-Objekt bietet Zugriff auf Instanzen der Schnittstellen IScreenObject und IScreenView. Über die IScreenView-Schnittstelle wird das Stamm-UI-Steuerelement dieses Bildschirms abgerufen. Die Beispielshell legt dann dieses Steuerelement als Inhalt des Registerkartenelements fest. Viele der Laufzeit-APIs verwenden IScreenObject auf die eine oder andere Weise, daher ist es sinnvoll, dieses Objekt als Datenkontext für das Steuerelement festzulegen, das die Benutzeroberfläche besitzt (in diesem Beispiel das Registerkartenelement). Jedoch fehlen in IScreenObject mehrere zentrale Funktionen. In diesem Beispiel ist daher eine Hilfsklasse mit dem Namen MyScreenObject enthalten, die diese Funktionen bereitstellt und folglich als Datenkontext für das Registerkartenelement fungiert.

So erstellen Sie die MyScreenObject-Klasse

  1. Öffnen Sie im Projektmappen-Explorer das Kontextmenü für den Ordner Präsentation, Shells, und wählen Sie dann Hinzufügen und Klasse aus.

  2. Geben Sie im Feld Name die Zeichenfolge MyScreenObject ein, und klicken Sie dann auf Hinzufügen.

  3. Ersetzen Sie den Inhalt der Klasse durch Folgendes.

    Imports System
    Imports System.Collections.Generic
    Imports System.ComponentModel
    Imports System.Linq
    Imports System.Windows
    
    Imports Microsoft.LightSwitch
    Imports Microsoft.LightSwitch.Client
    Imports Microsoft.LightSwitch.Details
    Imports Microsoft.LightSwitch.Details.Client
    Imports Microsoft.LightSwitch.Utilities
    
    Namespace Presentation.Shells
    
        Public Class MyScreenObject
            Implements IScreenObject
            Implements INotifyPropertyChanged
    
            Private screenObject As IScreenObject
            Private dirty As Boolean
            Private dataServicePropertyChangedListeners As List(Of IWeakEventListener)
    
            Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
    
            Friend Sub New(screenObject As IScreenObject)
                Me.screenObject = screenObject
                Me.dataServicePropertyChangedListeners = New List(Of IWeakEventListener)
    
                ' Register for property changed events on the details object.
                AddHandler CType(screenObject.Details, INotifyPropertyChanged).PropertyChanged, AddressOf Me.OnDetailsPropertyChanged
    
                ' Register for changed events on each of the data services.
                Dim dataServices As IEnumerable(Of IDataService) =
                    screenObject.Details.DataWorkspace.Details.Properties.All().OfType(Of IDataWorkspaceDataServiceProperty)().Select(Function(p) p.Value)
    
                For Each dataService As IDataService In dataServices
                    Me.dataServicePropertyChangedListeners.Add(CType(dataService.Details, INotifyPropertyChanged).CreateWeakPropertyChangedListener(Me, AddressOf Me.OnDataServicePropertyChanged))
                Next
            End Sub
    
            Private Sub OnDetailsPropertyChanged(sender As Object, e As PropertyChangedEventArgs)
                If String.Equals(e.PropertyName, "ValidationResults", StringComparison.OrdinalIgnoreCase) Then
                    RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("ValidationResults"))
                End If
            End Sub
    
            Private Sub OnDataServicePropertyChanged(sender As Object, e As PropertyChangedEventArgs)
                Dim dataService As IDataService = CType(sender, IDataServiceDetails).DataService
                Me.IsDirty = dataService.Details.HasChanges
            End Sub
    
            Friend ReadOnly Property RealScreenObject As IScreenObject
                Get
                    Return Me.screenObject
                End Get
            End Property
    
            Public Property IsDirty As Boolean
                Get
                    Return Me.dirty
                End Get
                Set(value As Boolean)
                    Me.dirty = value
                    RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("IsDirty"))
                End Set
            End Property
    
            Public ReadOnly Property ValidationResults As ValidationResults
                Get
                    Return Me.screenObject.Details.ValidationResults
                End Get
            End Property
    
            Public ReadOnly Property CanSave As Boolean Implements IScreenObject.CanSave
                Get
                    Return Me.screenObject.CanSave
                End Get
            End Property
    
            Public Sub Close(promptUserToSave As Boolean) Implements IScreenObject.Close
                Me.screenObject.Close(promptUserToSave)
            End Sub
    
            Public Property Description As String Implements IScreenObject.Description
                Get
                    Return Me.screenObject.Description
                End Get
                Set(value As String)
                    Me.screenObject.Description = value
                End Set
            End Property
    
            Public ReadOnly Property Details As IScreenDetails Implements IScreenObject.Details
                Get
                    Return Me.screenObject.Details
                End Get
            End Property
    
            Public Property DisplayName As String Implements IScreenObject.DisplayName
                Get
                    Return Me.screenObject.DisplayName
                End Get
                Set(value As String)
                    Me.screenObject.DisplayName = value
                End Set
            End Property
    
            Public ReadOnly Property Name As String Implements IScreenObject.Name
                Get
                    Return Me.screenObject.Name
                End Get
            End Property
    
            Public Sub Refresh() Implements IScreenObject.Refresh
                Me.screenObject.Refresh()
            End Sub
    
            Public Sub Save() Implements IScreenObject.Save
                Me.screenObject.Save()
            End Sub
    
            Public ReadOnly Property Details1 As IBusinessDetails Implements IBusinessObject.Details
                Get
                    Return CType(Me.screenObject, IBusinessObject).Details
                End Get
            End Property
    
            Public ReadOnly Property Details2 As IDetails Implements IObjectWithDetails.Details
                Get
                    Return CType(Me.screenObject, IObjectWithDetails).Details
                End Get
            End Property
    
            Public ReadOnly Property Details3 As IStructuralDetails Implements IStructuralObject.Details
                Get
                    Return CType(Me.screenObject, IStructuralObject).Details
                End Get
            End Property
        End Class
    
    End Namespace
    
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Windows;
    
    namespace ShellExtension.Presentation.Shells
    {
        using Microsoft.LightSwitch;
        using Microsoft.LightSwitch.Client;
        using Microsoft.LightSwitch.Details;
        using Microsoft.LightSwitch.Details.Client;
        using Microsoft.LightSwitch.Utilities;
    
        public class MyScreenObject : IScreenObject, INotifyPropertyChanged
        {
            private IScreenObject               screenObject;
            private bool                        dirty;
            private List<IWeakEventListener>    dataServicePropertyChangedListeners;
    
            public event PropertyChangedEventHandler    PropertyChanged;
    
            internal MyScreenObject(IScreenObject screenObject)
            {
                this.screenObject                           = screenObject;
                this.dataServicePropertyChangedListeners    = new List<IWeakEventListener>();
    
                // Register for property changed events on the details object.
                ((INotifyPropertyChanged)screenObject.Details).PropertyChanged += this.OnDetailsPropertyChanged;
    
                // Register for changed events on each of the data services.
                IEnumerable<IDataService>   dataServices = screenObject.Details.DataWorkspace.Details.Properties.All().OfType<IDataWorkspaceDataServiceProperty>().Select(p => p.Value);
    
                foreach(IDataService dataService in dataServices)
                    this.dataServicePropertyChangedListeners.Add(((INotifyPropertyChanged)dataService.Details).CreateWeakPropertyChangedListener(this, this.OnDataServicePropertyChanged));
            }
    
            private void OnDetailsPropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                if ( String.Equals(e.PropertyName, "ValidationResults", StringComparison.OrdinalIgnoreCase) )
                {
                    if ( null != this.PropertyChanged )
                        PropertyChanged(this, new PropertyChangedEventArgs("ValidationResults"));
                }
            }
    
            private void OnDataServicePropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                IDataService    dataService = ((IDataServiceDetails)sender).DataService;
    
                this.IsDirty = dataService.Details.HasChanges;
            }
    
            internal IScreenObject RealScreenObject
            {
                get
                {
                    return this.screenObject;
                }
            }
    
            public bool IsDirty
            {
                get
                {
                    return this.dirty;
                }
                set
                {
                    this.dirty = value;
    
                    if ( null != this.PropertyChanged )
                        PropertyChanged(this, new PropertyChangedEventArgs("IsDirty"));
                }
            }
    
            public ValidationResults ValidationResults
            {
                get
                {
                    return this.screenObject.Details.ValidationResults;
                }
            }
    
            public IScreenDetails Details
            {
                get
                {
                    return this.screenObject.Details;
                }
            }
    
            public string Name
            {
                get
                {
                    return this.screenObject.Name;
                }
            }
    
            public string DisplayName
            {
                get
                {
                    return this.screenObject.DisplayName;
                }
                set
                {
                    this.screenObject.DisplayName = value;
                }
            }
    
            public string Description
            {
                get
                {
                    return this.screenObject.Description;
                }
                set
                {
                    this.screenObject.Description = value;
                }
            }
    
            public bool CanSave
            {
                get
                {
                    return this.screenObject.CanSave;
                }
            }
    
            public void Save()
            {
                this.screenObject.Save();
            }
    
            public void Refresh()
            {
                this.screenObject.Refresh();
            }
    
            public void Close(bool promptUserToSave)
            {
                this.screenObject.Close(promptUserToSave);
            }
    
            IBusinessDetails IBusinessObject.Details
            {
                get
                {
                    return ((IBusinessObject)this.screenObject).Details;
                }
            }
    
            IStructuralDetails IStructuralObject.Details
            {
                get
                {
                    return ((IStructuralObject)this.screenObject).Details;
                }
            }
    
            IDetails IObjectWithDetails.Details
            {
                get
                {
                    return ((IObjectWithDetails)this.screenObject).Details;
                }
            }
        }
    }
    

Sobald die Bildschirme geöffnet sind, muss die Shell nachverfolgen, welcher Bildschirm aktuell ist. Um den aktuellen Bildschirm nachverfolgen zu können, legen Sie die Current-Eigenschaft auf ActiveScreensViewModel fest. Um einen geöffneten Bildschirm zu verfolgen, wird diese Eigenschaft auf die Instanz von IScreenObject festgelegt, die gerade geöffnet wurde. Mit dem folgenden Codesegment der ShellSample-Klasse wird der geöffnete Bildschirm verfolgt.

Public Sub OnScreenOpened(n As Notification)
            ' This method is called when a screen has been opened by the runtime.  In response to
            ' this, we need to create a tab item and set its content to be the UI for the newly
            ' opened screen.
            Dim screenOpenedNotification As ScreenOpenedNotification = n
            Dim screenObject As IScreenObject = screenOpenedNotification.Screen
            Dim view As IScreenView = Me.ServiceProxy.ScreenViewService.GetScreenView(screenObject)

            ' Create a tab item and bind its header to the display name of the screen.
            Dim ti As TabItem = New TabItem()
            Dim template As DataTemplate = Me.Resources("TabItemHeaderTemplate")
            Dim element As UIElement = template.LoadContent()

            ' The IScreenObject does not contain properties indicating if the screen has
            ' changes or validation errors.  As such, we have created a thin wrapper around the
            ' screen object that does expose this functionality.  This wrapper, a class called
            ' MyScreenObject, is what we'll use as the data context for the tab item.
            ti.DataContext = New MyScreenObject(screenObject)
            ti.Header = element
            ti.HeaderTemplate = template
            ti.Content = view.RootUI

            ' Add the tab item to the tab control.
            Me.ScreenArea.Items.Add(ti)
            Me.ScreenArea.SelectedItem = ti

            ' Set the currently active screen in the active screens view model.
            Me.ServiceProxy.ActiveScreensViewModel.Current = screenObject
        End Sub
public void OnScreenOpened(Notification n)
        {
            // This method is called when a screen has been opened by the run time.  In response to
            // this, create a tab item and set its content to be the UI for the newly
            // opened screen.
            ScreenOpenedNotification    screenOpenedNotification    = (ScreenOpenedNotification)n;
            IScreenObject               screenObject                = screenOpenedNotification.Screen;
            IScreenView                 view                        = this.ServiceProxy.ScreenViewService.GetScreenView(screenObject);

            // Create a tab item and bind its header to the display name of the screen.
            TabItem         ti          = new TabItem();
            DataTemplate    template    = (DataTemplate)this.Resources["TabItemHeaderTemplate"];
            UIElement       element     = (UIElement)template.LoadContent();

            // The IScreenObject does not contain properties indicating if the screen has
            // changes or validation errors.  As such, we have created a thin wrapper around the
            // screen object that does expose this functionality.  This wrapper, a class called
            // MyScreenObject, is what you will use as the data context for the tab item.
            ti.DataContext      = new MyScreenObject(screenObject);
            ti.Header           = element;
            ti.HeaderTemplate   = template;
            ti.Content          = view.RootUI;

            // Add the tab item to the tab control.
            this.ScreenArea.Items.Add(ti);
            this.ScreenArea.SelectedItem = ti;

            // Set the currently active screen in the active screens view model.
            this.ServiceProxy.ActiveScreensViewModel.Current = screenObject;
        }

Hh290138.collapse_all(de-de,VS.140).gifBehandeln der Bildschirminteraktion

Sobald Bildschirme geöffnet sind, müssen auch die Benutzerinteraktionen wie Schließen, Aktualisieren oder Wechseln zwischen aktiven Bildschirmen behandelt werden. Die Benachrichtigungen für das Schließen oder Aktualisieren eines Bildschirms enthalten das IScreenObject-Objekt, das geschlossen oder aktualisiert wird. Der Datenkontext für das den Bildschirm hostende Steuerelement ist tatsächlich der MyScreenObject-Wrapper um IScreenObject. Daher muss die zugrunde liegende Bildschirmobjektinstanz von der MyScreenObject-Instanz abgerufen werden, damit sie mit dem Bildschirmobjekt verglichen werden kann, das Teil der Benachrichtigungsargumente ist. Mit folgendem Code wird die zugrunde liegende IScreenObject-Instanz durch den Aufruf der RealScreenObject-Eigenschaft auf MyScreenObject aufgerufen.

Dim realScreenObject As IScreenObject = DirectCast(ti.DataContext, MyScreenObject).RealScreenObject
IScreenObject   realScreenObject = ((MyScreenObject)ti.DataContext).RealScreenObject;

Der grundlegende Workflow beim Schließen eines Bildschirms umfasst das Entfernen des Steuerelements, mit dem der gerade geschlossene Bildschirm angezeigt wurde. Zudem wird der zurzeit geöffnete Bildschirm zu den anderen noch geöffneten (sofern vorhanden) hinzugefügt. Im Beispiel wird beim Entfernen des Steuerelements nach dem entsprechenden Registerkartenelement gesucht und dieses aus dem übergeordneten Registerkarten-Steuerelement entfernt. Nach dieser Entfernung müssen Sie die Current-Eigenschaft auf ActiveScreensViewModel so festlegen, dass diese die zugrunde liegende IScreenObject-Instanz ist, die von der MyScreenObject-Instanz umschlossen wird, die wiederum den Datenkontext für das neu ausgewählte Registerkartenelement stellt. Im folgenden Codesegment der ShellSample-Klasse wird das Schließen eines Bildschirms behandelt.

Public Sub OnScreenClosed(n As Notification)
            ' A screen has been closed and therefore removed from the application's
            ' collection of active screens.  In response to this, we need to do
            ' two things:
            '  1.  Remove the tab item that was displaying this screen.
            '  2.  Set the "current" screen to the screen that will be displayed
            '      in the tab item that will be made active.
            Dim screenClosedNotification As ScreenClosedNotification = n
            Dim screenObject As IScreenObject = screenClosedNotification.Screen

            For Each ti As TabItem In Me.ScreenArea.Items
                ' We need to get the real IScreenObject from the instance of the MyScreenObject.
                Dim realScreenObject As IScreenObject = CType(ti.DataContext, MyScreenObject).RealScreenObject

                If realScreenObject Is screenObject Then
                    Me.ScreenArea.Items.Remove(ti)
                    Exit For
                End If
            Next

            ' If there are any tab items left, set the current tab to the last one in the list
            ' AND set the current screen to be the screen contained within that tab item.
            Dim count As Integer = Me.ScreenArea.Items.Count

            If count > 0 Then
                Dim ti As TabItem = Me.ScreenArea.Items(count - 1)

                Me.ScreenArea.SelectedItem = ti
                Me.ServiceProxy.ActiveScreensViewModel.Current = CType(ti.DataContext, MyScreenObject).RealScreenObject
            End If
        End Sub

Private Sub OnClickTabItemClose(sender As Object, e As RoutedEventArgs)
            ' When the user closes a tab, we simply need to close the corresponding
            ' screen object.  The only caveat here is that the call to the Close
            ' method needs to happen on the logic thread for the screen.  To do this
            ' we need to use the Dispatcher object for the screen.
            Dim screenObject As IScreenObject = TryCast(CType(sender, Button).DataContext, IScreenObject)

            If screenObject IsNot Nothing Then
                screenObject.Details.Dispatcher.EnsureInvoke(
                    Sub()
                        screenObject.Close(True)
                    End Sub)
            End If
        End Sub
public void OnScreenClosed(Notification n)
        {
            // A screen has been closed and therefore removed from the application's
            // collection of active screens.  In response to this, do
            // two things:
            //  1.  Remove the tab item that was displaying this screen.
            //  2.  Set the "current" screen to the screen that will be displayed
            //      in the tab item that will be made active.
            ScreenClosedNotification    screenClosedNotification    = (ScreenClosedNotification)n;
            IScreenObject               screenObject                = screenClosedNotification.Screen;

            foreach(TabItem ti in this.ScreenArea.Items)
            {
                // We need to get the real IScreenObject from the instance of the MyScreenObject.
                IScreenObject   realScreenObject = ((MyScreenObject)ti.DataContext).RealScreenObject;

                if ( realScreenObject == screenObject )
                {
                    this.ScreenArea.Items.Remove(ti);
                    break;
                }
            }

            // If there are any tab items left, set the current tab to the last one in the list
            // AND set the current screen to be the screen contained within that tab item.
            int count = this.ScreenArea.Items.Count;

            if ( count > 0 )
            {
                TabItem ti = (TabItem)this.ScreenArea.Items[count - 1];

                this.ScreenArea.SelectedItem = ti;
                this.ServiceProxy.ActiveScreensViewModel.Current = ((MyScreenObject)(ti.DataContext)).RealScreenObject;
            }
        }
private void OnClickTabItemClose(object sender, RoutedEventArgs e)
        {
            // When the user closes a tab, you simply need to close the corresponding
            // screen object.  The only caveat here is that the call to the Close
            // method needs to happen on the logic thread for the screen.  To enable this
            // use the Dispatcher object for the screen.
            IScreenObject screenObject = ((Button)sender).DataContext as IScreenObject;

            if (null != screenObject)
            {
                screenObject.Details.Dispatcher.EnsureInvoke(() =>
                {
                    screenObject.Close(true);
                });
            }
        }

Wird ein Bildschirm aktualisiert, wird er im Grunde beendet und erneut ausgeführt. Das heißt, IScreenObject für den Bildschirm ist ein neues Objekt. Die Argumente in der Benachrichtigung ScreenReloadedNotification enthalten die IScreenObject-Instanz für die vorherige Bildschirminstanz. Auf diese Weise können Sie das Steuerelement suchen, von dem der ursprüngliche Bildschirm gehostet wurde. Nachdem dieses Steuerelement ermittelt ist, muss dessen Datenkontext auf das neue IScreenObject-Element für den neuen Bildschirm festgelegt werden. In diesem Beispiel wird dafür ein neues MyScreenObject-Element erstellt, mit dem IScreenObject umschlossen wird. Das Element wird dann im Datenkontext für das Registerkarten-Steuerelement platziert. Im folgenden Codesegment der ShellSample-Klasse wird das Aktualisieren eines Bildschirms behandelt.

Public Sub OnScreenRefreshed(n As Notification)
            ' When a screen is refreshed, the runtime actually creates a new IScreenObject
            ' for it and discards the old one.  So in response to this notification what
            ' we need to do is replace the data context for the tab item that contains
            ' this screen with a wrapper (MyScreenObject) for the new IScreenObject instance.
            Dim srn As ScreenReloadedNotification = n

            For Each ti As TabItem In Me.ScreenArea.Items

                Dim realScreenObject As IScreenObject = CType(ti.DataContext, MyScreenObject).RealScreenObject

                If realScreenObject Is srn.OriginalScreen Then
                    Dim view As IScreenView = Me.ServiceProxy.ScreenViewService.GetScreenView(srn.NewScreen)

                    ti.Content = view.RootUI
                    ti.DataContext = New MyScreenObject(srn.NewScreen)
                    Exit For
                End If
            Next
        End Sub

Private Sub OnTabItemSelectionChanged(sender As Object, e As SelectionChangedEventArgs)
            ' When the user selects a tab item, we need to set the "active" screen
            ' in the ActiveScreensView model.  Doing this causes the commands view
            ' model to be udpated to reflect the commands of the current screen.
            If e.AddedItems.Count > 0 Then
                Dim selectedItem As TabItem = e.AddedItems(0)

                If selectedItem IsNot Nothing Then
                    Dim screenObject As IScreenObject = CType(selectedItem.DataContext, MyScreenObject).RealScreenObject

                    Me.ServiceProxy.ActiveScreensViewModel.Current = screenObject
                End If
            End If
        End Sub
public void OnScreenRefreshed(Notification n)
        {
            // When a screen is refreshed, the run time actually creates a new IScreenObject
            // for it and discards the old one.  So in response to this notification, 
            // you need to replace the data context for the tab item that contains
            // this screen with a wrapper (MyScreenObject) for the new IScreenObject instance.
            ScreenReloadedNotification  srn = (ScreenReloadedNotification)n;

            foreach(TabItem ti in this.ScreenArea.Items)
            {
                IScreenObject   realScreenObject = ((MyScreenObject)ti.DataContext).RealScreenObject;

                if ( realScreenObject == srn.OriginalScreen )
                {
                    IScreenView view = this.ServiceProxy.ScreenViewService.GetScreenView(srn.NewScreen);

                    ti.Content      = view.RootUI;
                    ti.DataContext  = new MyScreenObject(srn.NewScreen);
                    break;
                }
            }
        }
private void OnTabItemSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            // When the user selects a tab item, you need to set the "active" screen
            // in the ActiveScreensView model.  Doing this causes the commands view
            // model to be udpated to reflect the commands of the current screen.
            if (e.AddedItems.Count > 0)
            {
                TabItem selectedItem = (TabItem)e.AddedItems[0];

                if (null != selectedItem)
                {
                    IScreenObject screenObject = ((MyScreenObject)selectedItem.DataContext).RealScreenObject;

                    this.ServiceProxy.ActiveScreensViewModel.Current = screenObject;
                }
            }
        }

Hh290138.collapse_all(de-de,VS.140).gifImplementieren von Befehlen

In der Beispielshell werden die verfügbaren Befehle auf einer Befehlsleiste oben in der Shell angezeigt. Dabei handelt es sich einfach um ein standardmäßiges ListBox-Element von Silverlight. Der folgende Auszug aus der ShellSample.xaml-Datei zeigt die Implementierung.

<!-- The command panel is a horizontally oriented list box whose data context is set to the  -->
        <!-- CommandsViewModel.  The ItemsSource of this list box is data bound to the ShellCommands -->
        <!-- property.  This results in each item being bound to an instance of an IShellCommand.    -->
        <!--                                                                                         -->
        <!-- The attribute 'ShellHelpers:ComponentViewModelService.ViewModelName' is the manner by   -->
        <!-- which a control specifies the view model that is to be set as its data context.  In     -->
        <!-- case, the view model is identified by the name 'Default.CommandsViewModel'.             -->
        <ListBox x:Name="CommandPanel" Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" Background="{StaticResource RibbonBackgroundBrush}"
                        ShellHelpers:ComponentViewModelService.ViewModelName="Default.CommandsViewModel"
                        ItemsSource="{Binding ShellCommands}">

            <ListBox.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel Orientation="Horizontal" />
                </ItemsPanelTemplate>
            </ListBox.ItemsPanel>
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <!-- Each item in the list box will be a button whose content is the following:         -->
                    <!--    1.  An image, which is bound to the Image property of the IShellCommand         -->
                    <!--    2.  A text block whose text is bound the DisplayName of the IShellCommand   -->
                    <StackPanel Orientation="Horizontal">
                        <!-- The button will be enabled ordisabled according to the IsEnabled property of  the -->
                        <!-- IShellCommand.  The handler for the click event will execute the command.  -->
                        <Button Click="GeneralCommandHandler"
                                IsEnabled="{Binding IsEnabled}"
                                Style="{x:Null}"
                                Background="{StaticResource ButtonBackgroundBrush}"
                                Margin="1">

                            <Grid>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="32" />
                                    <RowDefinition MinHeight="24" Height="*"/>
                                </Grid.RowDefinitions>
                                <Image Grid.Row="0"
                                       Source="{Binding Image}"
                                       Width="32"
                                       Height="32"
                                       Stretch="UniformToFill"
                                       Margin="0"
                                       VerticalAlignment="Top"
                                       HorizontalAlignment="Center" />
                                <TextBlock Grid.Row="1"
                                           Text="{Binding DisplayName}"
                                           TextAlignment="Center"
                                           TextWrapping="Wrap"
                                           Style="{StaticResource TextBlockFontsStyle}"
                                           MaxWidth="64" />
                            </Grid>
                        </Button>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

Beachten Sie im XAML-Code die Datenbindung an CommandsViewModel und dessen verschiedene Eigenschaften, die über die in der Vorlage angegebenen Steuerelemente erfolgt. Jeder Befehl in ListBox wird mit Button angezeigt, dessen Inhalt ein Image-Steuerelement und TextBlock ist. Die IsEnabled-Eigenschaft der Schaltfläche ist an die IsEnabled-Eigenschaft von IShellCommand gebunden, und die Text-Eigenschaft des Textblocks ist auf die DisplayName-Eigenschaft von IShellCommand festgelegt.

Im Handler des Click-Ereignisses für Button führen Sie den Befehl aus, indem Sie die ExecuteableObject-Eigenschaft auf dem IShellCommand-Objekt abrufen und dann die ExecuteAsync-Methode dafür aufrufen. Im folgenden Codesegment der ShellSample-Klasse wird das Schließen eines Bildschirms behandelt.

Private Sub GeneralCommandHandler(sender As Object, e As RoutedEventArgs)
            ' This function will get called when the user clicks one of the buttons on
            ' the command panel.  The sender is the button whose data context is the
            ' IShellCommand.
            '
            ' In order to execute the command (asynchronously) we simply call the
            ' ExecuteAsync method on the ExecutableObject property of the command.
            Dim command As IShellCommand = CType(sender, Button).DataContext

            command.ExecutableObject.ExecuteAsync()
        End Sub
private void GeneralCommandHandler(object sender, RoutedEventArgs e)
        {
            // This function will get called when the user clicks one of the buttons on
            // the command panel.  The sender is the button whose data context is the
            // IShellCommand.
            //
            // In order to execute the command (asynchronously), simply call the
            // ExecuteAsync method on the ExecutableObject property of the command.
            IShellCommand   command = (IShellCommand)((Button)sender).DataContext;

            command.ExecutableObject.ExecuteAsync();
        }

Hh290138.collapse_all(de-de,VS.140).gifBehandeln von Datenänderungen und Datenvalidierung

In LightSwitch verfügt jeder Bildschirm über einen eigenen Datenarbeitsbereich. Im Falle, dass Daten in diesem Arbeitsbereich geändert werden, gilt für den Bildschirm der Zustand Geändert. IScreenObject stellt keine einfache Eigenschaft bereit, die Ihnen sagt, ob eine Änderung vorliegt oder nicht. Daher müssen Sie diese Informationen aus den Datendiensten abrufen, mit denen Daten für den Arbeitsbereich des Bildschirms bereitgestellt werden. Dies erreichen Sie, indem Sie eine Registrierung für die PropertyChanged-Ereignisse auf jedem Datendienst vornehmen. Die folgenden Codeauszüge stammen aus der MyScreenObject-Klasse. Sie wird in diesem Beispiel als Wrapper um die IScreenObject-Instanzen verwendet, um diese Funktionalität für Sie bereitzustellen.

' Register for changed events on each of the data services.
            Dim dataServices As IEnumerable(Of IDataService) =
                screenObject.Details.DataWorkspace.Details.Properties.All().OfType(Of IDataWorkspaceDataServiceProperty)().Select(Function(p) p.Value)

            For Each dataService As IDataService In dataServices
                Me.dataServicePropertyChangedListeners.Add(CType(dataService.Details, INotifyPropertyChanged).CreateWeakPropertyChangedListener(Me, AddressOf Me.OnDataServicePropertyChanged))
            Next
// Register for changed events on each of the data services.
        IEnumerable<IDataService>   dataServices = screenObject.Details.DataWorkspace.Details.Properties.All().OfType<IDataWorkspaceDataServiceProperty>().Select(p => p.Value);

        foreach(IDataService dataService in dataServices)
            this.dataServicePropertyChangedListeners.Add(((INotifyPropertyChanged)dataService.Details).CreateWeakPropertyChangedListener(this, this.OnDataServicePropertyChanged));

Der OnDataServicePropertyChanged-Handler überprüft wie folgt die HasChanges-Eigenschaft des Detailobjekts für den Datendienst.

Private Sub OnDataServicePropertyChanged(sender As Object, e As PropertyChangedEventArgs)
            Dim dataService As IDataService = CType(sender, IDataServiceDetails).DataService
            Me.IsDirty = dataService.Details.HasChanges
        End Sub
private void OnDataServicePropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            IDataService    dataService = ((IDataServiceDetails)sender).DataService;

            this.IsDirty = dataService.Details.HasChanges;
        }

Um die Validierungsfehler für einen Bildschirm anzuzeigen, müssen Sie zuerst feststellen, ob der Bildschirm einen Fehler aufweist. Diese Feststellung erfolgt nach denselben Schritten, mit denen Sie bestimmen, ob ein Bildschirm geändert wurde. Das heißt, IScreenObject stellt keine einfache Eigenschaft bereit, die diesen Zustand angibt. Das Detailobjekt für den Bildschirm enthält die Validierungsergebnisse. Wird dieses Resultset geändert, löst es eine PropertyChanged-Benachrichtigung aus. Daher ist nur eine Registrierung für diese Benachrichtigung erforderlich. Die folgenden Codeauszüge stammen aus der MyScreenObject-Klasse.

' Register for property changed events on the details object.
            AddHandler CType(screenObject.Details, INotifyPropertyChanged).PropertyChanged, AddressOf Me.OnDetailsPropertyChanged
// Register for property changed events on the details object.
        ((INotifyPropertyChanged)screenObject.Details).PropertyChanged += this.OnDetailsPropertyChanged;

Im Handler für diese Benachrichtigung überprüfen Sie, ob es sich bei der auf dem Detailobjekt geänderten Eigenschaft um die ValidationResults-Eigenschaft handelt. Ist das der Fall, wissen Sie, dass die Validierungsergebnisse geändert worden sind. In der Beispielshell ist die Visibility-Eigenschaft eines Textblocks im Registerkartenelement an die ValidationResults-Eigenschaft auf der MyScreenObject-Instanz gebunden. Im Handler für die PropertyChanged-Benachrichtigung wird die PropertyChanged-Benachrichtigung für die Eigenschaft auf MyScreenObject veröffentlicht. So wird der Text mit der Information, dass Validierungsfehler vorhanden sind, auf dem Registerkartenelement angezeigt.

Private Sub OnDetailsPropertyChanged(sender As Object, e As PropertyChangedEventArgs)
            If String.Equals(e.PropertyName, "ValidationResults", StringComparison.OrdinalIgnoreCase) Then
                RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("ValidationResults"))
            End If
        End Sub
private void OnDetailsPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if ( String.Equals(e.PropertyName, "ValidationResults", StringComparison.OrdinalIgnoreCase) )
            {
                if ( null != this.PropertyChanged )
                    PropertyChanged(this, new PropertyChangedEventArgs("ValidationResults"));
            }
        }

Wenn Sie im Beispiel mit dem Mauszeiger auf diesen Text zeigen (bei dem es sich nur um ein Ausrufezeichen handelt), wird eine QuickInfo generiert, die alle Fehler für den Bildschirm anzeigt. Dies wird im folgenden Auszug aus der ShellSample.xaml-Datei definiert.

<TextBlock Style="{StaticResource TextBlockFontsStyle}" Text="!" 
                   Visibility="{Binding ValidationResults.HasErrors, Converter={StaticResource ScreenHasErrorsConverter}}" 
                   Margin="5, 0, 5, 0" Foreground="Red" FontWeight="Bold">
            <ToolTipService.ToolTip>
                <ToolTip Content="{Binding ValidationResults, Converter={StaticResource ScreenResultsConverter}}" />
            </ToolTipService.ToolTip>
        </TextBlock>

Hh290138.collapse_all(de-de,VS.140).gifAnzeigen des aktuellen Benutzers

Bei aktivierter Authentifizierung in einer LightSwitch-Anwendung wäre es für die Benutzer hilfreich, wenn die Shell den Namen des Benutzers anzeigt, der derzeit die Anwendung nutzt. In der Beispielshell werden diese Informationen in der linken unteren Ecke angezeigt. Ist die Authentifizierung deaktiviert, wird der Wert "Die Authentifizierung ist nicht aktiviert" angezeigt. Im folgenden Auszug aus der ShellSample.xaml-Datei werden die Steuerelemente zur Anzeige dieser Informationen definiert.

<!-- The name of the current user is displayed in the lower-left corner of the shell. -->
        <Grid Grid.Column="0" Grid.Row="3" Grid.ColumnSpan="2">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>

            <TextBlock Grid.Column="0" Style="{StaticResource TextBlockFontsStyle}" Text="Current User: " Foreground="{StaticResource NormalFontBrush}"/>

            <!-- This TextBlock has its data context set to the CurrentUserViewModel, from which the -->
            <!-- CurrentUserDisplayName property is used to provide the name of the user displayed.  -->
            <TextBlock Grid.Column="1"
                       Style="{StaticResource TextBlockFontsStyle}"
                       ShellHelpers:ComponentViewModelService.ViewModelName="Default.CurrentUserViewModel"
                       Text="{Binding CurrentUserDisplayName, Converter={StaticResource CurrentUserConverter}}"
                       Foreground="{StaticResource NormalFontBrush}"/>
        </Grid>

Die Angabe des aktuellen Benutzers wird über das CurrentUserViewModel-ViewModel bereitgestellt. Der Textblock, in dem der Name des aktuellen Benutzers angegeben wird, ist an die CurrentUserDisplayName-Eigenschaft von diesem ViewModel gebunden.

Angeben eines Anzeigenamens und einer Beschreibung

Der Name und die Beschreibung für die Shell werden in der ShellSample.lsml-Datei definiert. Die Standardwerte lauten "ShellSample" und "ShellSample description". Diese werden dem Benutzer der Shell im Anwendungs-Designer verfügbar gemacht. Daher ändern Sie dies in aussagekräftigere Bezeichnungen ab.

So aktualisieren Sie den Namen und die Beschreibung

  1. Wählen Sie im Projektmappen-Explorer das Projekt ShellExtension.Common aus.

  2. Erweitern Sie die Knoten Metadaten und Shells, und öffnen Sie die Datei ShellSample.lsml.

  3. Ersetzen Sie wie folgt im Shell.Attributes-Element die Werte DisplayName und Description.

    <Shell.Attributes>
          <DisplayName Value="My Sample Shell"/>
          <Description Value="This is my first example of a shell extension."/>
        </Shell.Attributes>
    

    Hinweis

    Sie können diese Werte auch als Ressourcen in der ModuleResources.resx-Datei speichern.Weitere Informationen finden Sie unter "So aktualisieren Sie Steuerelement-Metadaten" in Exemplarische Vorgehensweise: Erstellen einer Detailsteuerelementerweiterung.

Testen der Shellerweiterung

Sie können die Geschäftstyperweiterung in einer experimentellen Instanz von Visual Studio testen. Falls Sie nicht bereits ein anderes LightSwitch-Erweiterungsprojekt getestet haben, müssen Sie die experimentelle Instanz zunächst aktivieren.

So aktivieren Sie eine experimentelle Instanz

  1. Wählen Sie im Projektmappen-Explorer das BusinessTypeExtension.Vsix-Projekt aus.

  2. Wählen Sie in der Menüleiste Projekt und dann die Option für Eigenschaften von BusinessTypeExtension.Vsix aus.

  3. Wählen Sie auf der Registerkarte Debuggen unter Startaktion die Option Externes Programm starten aus.

  4. Geben Sie den Pfad der ausführbaren Visual Studio-Datei (devenv.exe) ein.

    Auf einem 32-Bit-System ist der Standardpfad C:\Programme\Microsoft Visual Studio 12.0\Common7\IDE\devenv.exe, und auf einem 64-Bit-System ist der Pfad C:\Programme (x86)\Microsoft Visual Studio 12.0\Common7\IDE\devenv.exe.

  5. Geben Sie im Feld Befehlszeilenargumente die Zeichenfolge /rootsuffix Exp ein.

    Hinweis

    Alle folgenden LightSwitch-Erweiterungsprojekte verwenden standardmäßig ebenfalls diese Einstellung.

So testen Sie die Shell

  1. Klicken Sie in der Menüleiste auf Debuggen und dann auf Debuggen starten. Eine experimentelle Instanz von Visual Studio wird geöffnet.

  2. Wählen Sie in der experimentellen Instanz in der Menüleiste Datei, Neu und Projekt aus.

  3. Wählen Sie im Dialogfeld Projekt öffnen ein beliebiges vorhandenes LightSwitch-Anwendungsprojekt und dann die Schaltfläche OK aus.

  4. Wählen Sie in der Menüleiste Projekt und die Option für ProjectName-Eigenschaften aus.

  5. Aktivieren Sie im Projekt-Designer auf der Registerkarte Erweiterungen das Kontrollkästchen ShellExtension.

  6. Wählen Sie auf der Registerkarte Allgemeine Eigenschaften in der Liste Shell die Option Meine Beispielshell aus.

  7. Klicken Sie in der Menüleiste auf Debuggen und dann auf Debuggen starten.

    Beachten Sie, dass es keinen Standardbildschirm gibt. Um einen Bildschirm zu öffnen, müssen Sie auf ein Element im Bereich Navigation doppelklicken.

Nächste Schritte

Damit ist die exemplarische Vorgehensweise für die Shellerweiterung beendet. Sie sollten nun über eine voll funktionsfähige Shellerweiterung verfügen, die Sie in jedem LightSwitch-Projekt einsetzen können. Das war nur ein Beispiel für eine Shellerweiterung, möglicherweise möchten Sie eine Shell erstellen, die sich in Bezug auf Verhalten oder Layout erheblich unterscheidet. Die gleichen Schritte und Grundsätze gelten auch für jede andere Shellerweiterung, jedoch gibt es weitere Konzepte, die in anderen Situationen Anwendung finden.

Wenn Sie die Erweiterung verteilen möchten, sind einige zusätzliche Schritte erforderlich. Um die Richtigkeit der Informationen sicherzustellen, die für die Erweiterung im Projekt-Designer und Erweiterungs-Manager angezeigt werden, müssen die Eigenschaften für das VSIX-Paket aktualisiert werden. Weitere Informationen finden Sie unter Gewusst wie: VSIX-Paketeigenschaften. Außerdem sind bei einer geplanten öffentlichen Verteilung der Erweiterung einige Punkte zu berücksichtigen. Weitere Informationen finden Sie unter Gewusst wie: Verteilen einer LightSwitch-Erweiterung.

Siehe auch

Aufgaben

Gewusst wie: Verteilen einer LightSwitch-Erweiterung

Gewusst wie: VSIX-Paketeigenschaften

Konzepte

LightSwitch-Erweiterbarkeits-Toolkit für Visual Studio 2013