Динамический шаблон данных в приложении PowerShell WPF - PullRequest
0 голосов
/ 26 августа 2018

Я создал простое приложение WPF с использованием XAML и PowerShell, состоящее из TabControl, в чьих дочерних элементах TabItems я хочу отображать данные нескольких типов. В зависимости от типа предоставленных данных, я хочу, чтобы дочерние TabItems TabControl использовали другой DataTemplate.

Я понимаю, что лучший (только?) Способ сделать это в моей ситуации - создать собственный класс DataTemplateSelector в C # для обработки выбора шаблона.

Я пытался сделать это, но у меня возникают трудности с использованием моего пользовательского класса. Вот ошибка, которую я получаю:

Exception calling "Load" with "1" argument(s): "Cannot create unknown type
'{clr-namespace:myNamespace}myDataTemplateSelector'."
At line:101 char:1
+ $Window = [Windows.Markup.XamlReader]::Load($Reader)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : XamlParseException

Я подозреваю, что неправильно загружаю требуемую сборку или пространство имен и, следовательно, не могу получить доступ к своему пользовательскому пространству имен и пользовательскому классу. Я никогда раньше не пользовался C #, поэтому я очень ценю любые предложенные руки.

Как только я решил эту проблему, я знаю, что внутренняя логика моего пользовательского класса C # не будет работать должным образом, но это отдельная проблема. Код C # выглядит корректным, так как я могу запустить его независимо и создать свой собственный класс.

XAML и код также работают нормально, если я удаляю все биты, связанные с DataTemplateSelector, и добавляю в TabControl следующее:

ContentTemplate="{StaticResource UserDataTemplate}"

Вот код (включая C #, XAML, PowerShell):

$Assemblies = @("System", "PresentationFramework", "WindowsBase", "System.Xaml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")

$cSharpSource = @"
using System;
using System.Windows;
using System.Windows.Controls;

namespace myNamespace
{
    public class myDataTemplateSelector : DataTemplateSelector
    {
        public DataTemplate UserDataTemplate
        { get; set; }

        public DataTemplate GroupDataTemplate
        { get; set; }

        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {   
            if (item as string == "User")
            {
                return UserDataTemplate;
            }
            else if (item as string == "Group")
            {
                return GroupDataTemplate;
            }
            else
            {
                return null;
            }
        }
    }
}
"@

Add-Type -TypeDefinition $cSharpSource -ReferencedAssemblies $Assemblies

Add-Type -AssemblyName PresentationFramework

[xml]$XAML = @"
<Window x:Name="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="650" Width="300" FontSize="11"
    xmlns:local="clr-namespace:myNamespace">

    <Window.Resources>
        <DataTemplate x:Key="HeaderTemplate">
            <Label Content="Header text" />
        </DataTemplate>

        <DataTemplate x:Key="UserDataTemplate">
            <Grid>
                <TextBlock Text="UserDataTemplate in use" />
            </Grid>                
        </DataTemplate>

        <DataTemplate x:Key="GroupDataTemplate">
            <Grid>
                <TextBlock Text="GroupDataTemplate in use" />
            </Grid>                
        </DataTemplate>
    </Window.Resources>

    <StackPanel>

        <Button x:Name="UserTabItem_Button" Content="Load UserTabItem" />

        <Button x:Name="GroupTabItem_Button" Content="Load GroupTabItem" />

        <TabControl x:Name="TabControl" ItemTemplate="{StaticResource HeaderTemplate}">

            <TabControl.ContentTemplateSelector>
                <local:myDataTemplateSelector 
                    UserDataTemplate="{StaticResource UserDataTemplate}"
                    GroupDataTemplate="{StaticResource GroupDataTemplate}"/>
            </TabControl.ContentTemplateSelector>

        </TabControl>

    </StackPanel>
</Window>
"@


# Parse the XAML
$Reader = (New-Object System.Xml.XmlNodeReader $XAML)
$Window = [Windows.Markup.XamlReader]::Load($Reader)

# Iterate through each XAML node and create a variable for each node
$XAML.SelectNodes("//*[@*[contains(translate(name(.),'n','N'),'Name')]]") | ForEach-Object {
    New-Variable -Name $_.Name -Value $Window.FindName($_.Name) -Force
}

# Example data
$UserTabItem = [PSCustomObject]@{
    'ObjectClass' = 'User'
}

$GroupTabItem = [PSCustomObject]@{
    'ObjectClass' = 'Group'
}

# Clicks to add Child TabItems to TabControl
$UserTabItem_Button.Add_Click({
    $TabControl.AddChild($UserTabItem)
})

$GroupTabItem_Button.Add_Click({
    $TabControl.AddChild($GroupTabItem)
})

$Window.ShowDialog()

Я также исследовал хранение XAML DataTemplates в качестве переменных PowerShell и установку свойства ContentTemplate TabControl на соответствующий DataTemplate перед добавлением Child. Это было неудачно и, возможно, невозможно после прочтения документации шаблонов WPF.

Я открыт для других подходов. Спасибо за ваше время.

Ответы [ 2 ]

0 голосов
/ 26 августа 2018

Как подсказал рецепт Slime, создание библиотеки управления в Visual Studio сначала было гораздо менее болезненным.

Я создал новый проект «Библиотека классов (.NET Framework)» в Visual Studio, вставил в решение существующий синтаксически допустимый код C #, добавил ссылки на соответствующие сборки и создал проект.

Я скопировал полученный файл myDataTemplateSelector.dll в тот же каталог, где находится файл сценария PowerShell.

Я загрузил свежую консоль PowerShell (повторное использование консоли не загрузило сборку правильно) и выполнил следующие команды для проверки DLL:

Add-Type -Path .\myDataTemplateSelector.dll
[myNamespace.myDataTemplateSelector]::New()

Это успешно создало экземпляр моего пользовательского класса.

Наконец, я обновил свой XAML:

xmlns:local="clr-namespace:myNamespace;assembly=myDataTemplateSelectorLibrary"

Приложение WPF теперь работает!

Буду признателен за любые другие ответы, которые могут объяснить, как выполнить то же самое без необходимости компилировать код C # в Visual Studio, поскольку я бы предпочел не полагаться на нечитаемый человеком файл (то есть файл DLL) в этот проект.

Редактировать - полностью ответил:

Предложение рецепта Slice для программного поиска имени сборки вместо того, чтобы полагаться на то, что, как я предполагал, будет (на основе моего кода C #), привело меня по правильному пути. Еще раз спасибо.

При запуске кода C # в PowerShell обычно используется Add-Type, который загружает код только в память.

Если вы укажете исходный код, Add-Type скомпилирует указанный исходный код и сгенерирует сборку в памяти, содержащую новые типы .NET Framework.

https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/add-type?view=powershell-5.1

Чтобы получить доступ к метаданным для добавленного кода, необходимо использовать параметр -Passthru в Add-Type:

-PassThru

Возвращает объект System.Runtime, представляющий добавленные типы. По умолчанию [Add-Type] не генерирует никакого вывода.

Затем я сохранил вывод исправленной команды Add-Type:

$Type = Add-Type -TypeDefinition $cSharpSource -ReferencedAssemblies $Assemblies -PassThru

Полное имя сборки и доступ:

> $Type.Assembly.Fullname
m0m5m4la, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null

Первая строка «m0m5m4la» - это необходимое имя сборки, которое создается случайным образом при добавлении типа и действует как ссылка на сборку в памяти.

Наконец, к нему можно получить доступ при запуске сценария и вставить в XAML:

...
$Type = Add-Type -TypeDefinition $cSharpSource -ReferencedAssemblies $Assemblies -PassThru
$AssemblyName = $Type.Assembly.Fullname.Split(",",2)[0] 

Add-Type -AssemblyName PresentationFramework

[xml]$XAML = @"
<Window x:Name="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="650" Width="300" FontSize="11"
    xmlns:local="clr-namespace:myNamespace;assembly=$($AssemblyName)">
...

Я не уверен, насколько это хакерски, но это работает и позволяет всему коду оставаться в незашифрованном виде, и для продолжения разработки не требуется никаких программных средств (кроме текстового редактора).

Возможно, я недостаточно хорошо искал, но не смог найти ни одного примера такого рода вещей в Интернете. Надеюсь, это кому-нибудь поможет!

0 голосов
/ 26 августа 2018

Необходимо указать сборку и пространство имен. local = "clr-namespace: myNamespace" просто укажите пространство имен. Ты не уверен, как это работает в среде PS.

https://docs.microsoft.com/en-us/dotnet/framework/wpf/advanced/xaml-namespaces-and-namespace-mapping-for-wpf-xaml

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...