web-dev-qa-db-fra.com

Chargement de XAML à l'exécution?

Tout d’abord un peu d’arrière-plan: je travaille sur une application et j’essaie de suivre les conventions de MVVM pour l’écrire. Une chose que j'aimerais faire est de pouvoir attribuer à l'application différents "skins". La même application, mais affiche un "skin" pour un client et un "skin" différent pour un autre.

Et donc mes questions sont: 
1. Est-il possible de charger un fichier xaml au moment de l'exécution et de "l'assigner" à mon application? 
2. Le fichier xaml peut-il être un fichier externe résidant dans un autre dossier? 
3. L'application peut-elle basculer facilement vers un autre fichier xaml ou uniquement au démarrage?

Alors, où devrais-je commencer à chercher des informations à ce sujet? Quelles méthodes WPF, si elles existent, gèrent cette fonctionnalité?

Merci!

Edit: le type de "skinning" que je veux faire est plus que changer l'apparence de mes contrôles. L'idée est d'avoir une interface utilisateur complètement différente. Différents boutons, différentes dispositions. Un peu comme comment une version de l'application serait entièrement en vedette pour les experts et une autre version serait simplifiée pour les débutants.

45
djcouchycouch

Je pense que cela est assez simple avec XamlReader. Donnez un coup d’essai, n’essayez pas moi-même, mais je pense que cela devrait fonctionner.

http://blogs.msdn.com/ashish/archive/2007/08/14/dynamically-loading-xaml.aspx

17
Carlo

Comme Jakob Christensen l'a noté, vous pouvez charger n'importe quel XAML à l'aide de XamlReader.Load. Cela ne s'applique pas seulement aux styles, mais à UIElements également. Vous venez de charger le XAML comme:

UIElement rootElement;
FileStream s = new FileStream(fileName, FileMode.Open);
rootElement = (UIElement)XamlReader.Load(s);
s.Close();

Ensuite, vous pouvez le définir comme contenu de l'élément approprié, par exemple. pour

<Window x:Class="MainWindow"
    xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
    Title="Foo Bar">
    <Grid x:Name="layoutGrid">
        <!-- any static elements you might have -->
    </Grid>
</Window>

vous pouvez ajouter la rootElement dans la grid avec:

layoutGrid.Children.Add(rootElement);
layoutGrid.SetColumn(rootElement, COLUMN);
layoutGrid.SetRow(rootElement, ROW);

Naturellement, vous devrez également connecter manuellement tous les événements d'éléments dans la variable rootElement dans le code-behind. Par exemple, en supposant que votre rootElement contient une Canvas avec un groupe de Paths, vous pouvez affecter l'événement Paths 'MouseLeftButtonDown comme suit:

Canvas canvas = (Canvas)LogicalTreeHelper.FindLogicalNode(rootElement, "canvas1");
foreach (UIElement ui in LogicalTreeHelper.GetChildren(canvas)) {
    System.Windows.Shapes.Path path = ui as System.Windows.Shapes.Path;
    if (path != null) {
        path.MouseLeftButtonDown += this.LeftButtonDown;
    }
}

Je n'ai pas essayé de changer de fichier XAML à la volée, je ne peux donc pas dire si cela fonctionnera vraiment ou non.

36
Tomi Junnila

J'ai fait une simple extension de balisage, qui charge xaml:

public class DynamicXamlLoader : MarkupExtension
{
    public DynamicXamlLoader() { }

    public DynamicXamlLoader(string xamlFileName)
    {
        XamlFileName = xamlFileName;
    }

    public string XamlFileName { get; set; }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        var provideValue = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
        if (provideValue == null || provideValue.TargetObject == null) return null;

        // get target
        var targetObject = provideValue.TargetObject as UIElement;
        if (targetObject == null) return null;

        // get xaml file
        var xamlFile = new DirectoryInfo(Directory.GetCurrentDirectory())
            .GetFiles(XamlFileName ?? GenerateXamlName(targetObject), SearchOption.AllDirectories)
            .FirstOrDefault();

        if (xamlFile == null) return null;

        // load xaml
        using (var reader = new StreamReader(xamlFile.FullName))
            return XamlReader.Load(reader.BaseStream) as UIElement;
    }

    private static string GenerateXamlName(UIElement targetObject)
    {
        return string.Concat(targetObject.GetType().Name, ".xaml");
    }
}

Usage:

Cette recherche et charger le fichier MyFirstView.xaml

<ContentControl Content="{wpf:DynamicXamlLoader XamlFileName=MyFirstView.xaml}" />

Et cela remplit UserControl (recherche et chargement du fichier MySecondView.xaml)

<UserControl x:Class="MySecondView"
         xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
         Content="{wpf:DynamicXamlLoader}" />
5
tom.maruska

Vous pouvez charger n'importe quel XAML que vous voulez en utilisant XamlReader.Load

Si vous stylisez tous vos contrôles dans votre application et définissez ces styles dans le dictionnaire de ressources de votre application, vous pouvez charger les nouveaux styles définis dans XAML ailleurs en utilisant XamlReader.Load et remplacer des parties de votre dictionnaire de ressources par le XAML chargé. Vos commandes vont changer d'apparence en conséquence.

5

J'ai fini de charger XAML au moment de l'exécution, en voici un court exemple.

Grid grd = new Grid();
var grdEncoding = new ASCIIEncoding();
var grdBytes = grdEncoding.GetBytes(myXAML);
grd = (Grid)XamlReader.Load(new MemoryStream(grdBytes));
Grid.SetColumn(grd, 0);
Grid.SetRow(grd, 0);
parentGrid.Children.Add(grd);

private String myXAML = @" <Grid xmlns='http://schemas.Microsoft.com/winfx/2006/xaml/presentation' Margin='30 10 30 65' VerticalAlignment='Bottom'>" +
                    "<Label Content='Date: 1-Feb-2013' FontFamily='Arial' FontSize='12' Foreground='#666666' HorizontalAlignment='Left'/>" +
                    "<Label Content='4'  FontFamily='Arial' FontSize='12' Foreground='#666666' HorizontalAlignment='Center'/>" +
                    "<Label Content='Hello World'  FontFamily='Arial' FontSize='12' Foreground='#666666' HorizontalAlignment='Right'/>" +
                "</Grid>";
4
Rahul Saksule

Comme cela a déjà été mentionné dans d'autres réponses, vous pouvez utiliser XamlReader.Load

Si vous recherchez un exemple plus simple, voici un exemple montrant à quel point il est facile de créer un contrôle à partir d'une variable chaîne contenant le code XAML:

public T LoadXaml<T>(string xaml)
{
    using (var stringReader = new System.IO.StringReader(xaml))
    using (var xmlReader = System.Xml.XmlReader.Create(stringReader))
        return (T)System.Windows.Markup.XamlReader.Load(xmlReader);
}

Et comme l'usage:

var xaml = "<TextBox xmlns='http://schemas.Microsoft.com/winfx/2006/xaml/presentation\'>" +
            "Lorm ipsum dolor sit amet." +
            "</TextBox>";
var textBox = LoadXaml<System.Windows.Controls.TextBox>(xaml);
0
Reza Aghaei

Consultez http://www.codeproject.com/Articles/19782/Creating-a-Skinned-User-Interface-in-WPF - Josh Smith a écrit un excellent article sur la procédure de skinning dans WPF.

0
Paul Betts