web-dev-qa-db-fra.com

Héritage XAML UserControl

Venant de Java, je suis vraiment habitué à une pratique courante quand il s'agit de créer des composants GUI: je fais généralement une sorte de classe de base qui contient tous les objets communs pour mes composants GUI, puis je l'étends.

Donc, en gros, c'est ce que j'aimerais réaliser avec C # et XAML.

Pour clarifier la question, voici un exemple (qui ne fonctionne pas!) De ce que je fais:

Nous avons une classe de base avec son propre XAML

<UserControl x:Class="BaseClass"
    xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.Microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    d:DesignHeight="480" d:DesignWidth="480">

    <Grid x:Name="LayoutRoot" Background="{StaticResource PhoneChromeBrush}">
        <Border BorderBrush="Aqua" BorderThickness="10" CornerRadius="10" x:Name="Border" HorizontalAlignment="Left" Height="480" VerticalAlignment="Top" Width="480"/>

    </Grid>
</UserControl>

puis nous avons une classe qui étend la première

<base:BaseClass x:Class="DerivedClass"
    xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.Microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:base="clr-namespace:BaseClass"
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    d:DesignHeight="60" d:DesignWidth="200">

    <Grid x:Name="LayoutRoot" Margin="0" Width="200" Height="60" MaxWidth="200" MaxHeight="60" Background="{StaticResource PhoneAccentBrush}">        
        <TextBlock x:Name="dummyText" HorizontalAlignment="Left" Margin="10,10,0,0" TextWrapping="Wrap" Text="Dummy Plugin" VerticalAlignment="Top" Height="40" Width="180" Foreground="White" TextAlignment="Center"/>
    </Grid>
</base:BaseClass>

À partir des 2 codes XAML, ce que j'aimerais faire, c'est d'avoir le DerivedClass dans le conteneur BaseClass. Cela me permettra de partager des composants entre les différentes classes dérivées sans avoir à écrire le code chaque fois que j'en ai besoin.

Par exemple, si je veux que tous mes composants aient cette bordure arrondie, je voudrais simplement le mettre dans la classe de basse et ensuite l'avoir dans tous ceux dérivés sans avoir à le réécrire.

Bien sûr, chaque classe c # a sa propre méthode InitializeComponent() et cela signifie probablement que le composant dérivé va construire son propre contenu en supprimant celui de la classe de base.

La suppression de la méthode du constructeur DerivedClass me donne le contenu de base même dans la classe dérivée, mais, bien sûr, je perds tout ce que j'ai fait dans la fenêtre de conception XAML du DerivedClass.

L'appel du constructeur de base à partir de DerivedClass n'a aucun effet, car il est appelé avant la fonction InitializeComponent() dérivée.

La question est donc: comment utiliser la conception XAML d'une classe de base dans la classe dérivée sans casser la conception XAML de la classe dérivée? Existe-t-il un moyen d'ajouter simplement du contenu à la classe de base tout en travaillant avec le concepteur lui-même?

(Je sais que je peux supprimer le XAML pour la classe dérivée et faire ce que je veux faire par code, mais je veux savoir si je peux le faire uniquement avec le concepteur car je ne veux pas écrire mon interface graphique lorsque j'ai un designer disponible)

ÉDITER:

Suite à la réponse de HighCore, j'ai fait quelque chose qui fonctionne sur Windows Phone, mais je ne suis pas sûr de faire la bonne chose (oui, cela fonctionne, mais peut-être est-ce juste faux!).

Voici ce que j'ai fait:

BaseControl.xaml

<UserControl x:Class="TestInheritance.BaseControl"
    xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.Microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    d:DesignHeight="480" d:DesignWidth="480">


     <Grid x:Name="LayoutRoot" Background="{StaticResource PhoneChromeBrush}">        
        <TextBlock HorizontalAlignment="Center">BASE</TextBlock>        
        <ContentPresenter Name="Presenter" Content="{Binding PresenterContent}"/>
    </Grid>
</UserControl>

BaseControl.xaml.cs

namespace TestInheritance
{
    public partial class BaseControl : UserControl
    {

        public Grid PresenterContent { get; set; }        

        public BaseControl()
        {
            DataContext = this;
            InitializeComponent();            
        }
    }
}

DerivedControl.xaml

<local:BaseControl x:Class="TestInheritance.DerivedControl"
    xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.Microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:TestInheritance"
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    d:DesignHeight="480" d:DesignWidth="480">

    <local:BaseControl.PresenterContent>
        <Grid>
            <TextBlock VerticalAlignment="Bottom" HorizontalAlignment="Center">DERIVED</TextBlock>
        </Grid>
    </local:BaseControl.PresenterContent>
</local:BaseControl>

Veuillez noter que DerivedClass est une instance de BaseClass car j'en ai besoin pour avoir des propriétés/méthodes communes pour d'autres raisons.

Que pensez-vous de ma solution? Est-ce que ça fait du sens?

24
StepTNT

Ok, permettez-moi de diviser cela en plusieurs parties:

Venant de Java

Oubliez Java. C'est une langue vraiment archaïque qui n'a pas évolué depuis les années 90. C # est un million de fois meilleur et WPF est le meilleur framework d'interface utilisateur à ce jour.

d'après ce que j'ai vu, Java Les frameworks d'interface utilisateur tels que swing sont conceptuellement similaires aux winforms de .Net, qui ont également été remplacés par WPF.

WPF (et ses frères basés sur XAML) sont fondamentalement différents de tout autre framework en raison de leur capacité améliorée de personnalisation via Styles et modèles et de la prise en charge de DataBinding .

Pour cette raison, un Mindshift significatif est requis lors du démarrage sur WPF.


Je fais généralement une sorte de classe de base qui contient tous les objets communs pour mes composants GUI, puis je l'étends.

Dans WPF, il y a le modèle de conten , qui supprime le besoin d'héritage et d'autres pratiques inutiles, en introduisant la capacité de mettre "n'importe quoi à l'intérieur de n'importe quoi".

Par exemple, un Button peut être défini comme ceci:

<Button>
    <Button.Content>
        <StackPanel Orientation="Horizontal">
            <Ellipse Fill="Red" Height="10" Width="10" Margin="2"/>
            <TextBlock Text="Click Me"/>
        </StackPanel>
    <Button.Content>
 </Button>

ce qui se traduit par

A button with a red dot

Il n'est pas nécessaire d'hériter de Button simplement pour définir son contenu.

Il y a un avantage supplémentaire que WPF fournit et est vraiment pratique, l'attribut ContentProperty qui définit le contenu des balises XAML <Button> </Button> représente. Button est dérivé de ContentControl , qui est déclaré comme ceci:

//Declaration of the System.Windows.Control.ContentControl class,
//inside the PresentationFramework.dll Assembly
//...  
[ContentProperty("Content")]
public class ContentControl: Control //...
{
   //...
}

Cela signifie que le XAML suivant est fonctionnellement identique au précédent:

<Button>
   <StackPanel Orientation="Horizontal">
       <Ellipse Fill="Red" Height="10" Width="10" Margin="2"/>
       <TextBlock Text="Click Me"/>
    </StackPanel>
</Button>
  • Notez que nous avons supprimé le <Button.Content> tag, car l'attribut ContentProperty s'en charge.

Tout cela est rendu possible grâce à une fonctionnalité appelée ControlTemplates , qui définit l'apparence visuelle d'un contrôle, indépendamment de son comportement.


ce que j'aimerais faire, c'est d'avoir la DerivedClass dans le conteneur BaseClass.

Il existe plusieurs façons d'y parvenir, l'une d'elles consiste à tirer parti de ControlTemplates et à définir un conteneur spécifique à l'intérieur du XAML qui hébergera le contenu:

<UserControl x:Class="BaseClass">
    <UserControl.Template>
        <ControlTemplate TargetType="UserControl">
            <DockPanel>
                <TextBlock DockPanel.Dock="Top" Text="I'm the Container"/>

                <!-- This is where the Hosted Content will be placed -->
                <ContentPresenter ContentSource="Content"/>
            </DockPanel>
        </ControlTemplate>
     </UserControl.Template>
</UserControl>

Ensuite, vous pouvez réutiliser ce modèle comme ceci:

<Window>
   <my:BaseClass>
       <Border Background="Gray" BorderBrush="Blue" BorderThickness="2"
               VerticalAlignment="Center" HorizontalAlignment="Center">
           <TextBlock Text="Im the Hosted Content" Foreground="AliceBlue"/>
       </Border>
   </my:BaseClass>
</Window>

ce qui se traduit par:

An application window

Pas besoin d'héritage ni de code de procédure.


Un autre aspect très important lors du démarrage de WPF, qui est expliqué en détail dans le lien "Significant Mindshift" ci-dessus, est ce que je dis à tout le monde ici:

Apprenez MVVM avant d'écrire une seule ligne de code dans WPF

  • La plupart du temps, vous ne mettez pas de code dans les éléments de l'interface utilisateur WPF , car la plupart des choses peuvent être réalisées par DataBinding (couvert dans le lien "DataBinding" ci-dessus), ou en implémentant Réutilisable Comportements attachés ou Propriétés attachées . Seul le code spécifique à VIEW doit être placé derrière le code, qui ne traite pas des données ou de la logique métier

  • Le passe-partout auquel vous pourriez être habitué dans d'autres cadres, tels que:

    txtLastName.Text = person.LastName;
    txtFirstName.Text = person.FirstName;
    btnSubmit.IsEnabled = person.IsActive;
    

    et des trucs comme ça, est complètement inutile dans WPF, encore une fois, à cause de DataBinding.


WPF DataTemplates , qui permet de définir une interface utilisateur spécifique à utiliser lorsqu'un certain type de données est "rendu", est un autre concept qui permet une grande flexibilité lorsqu'il s'agit d'afficher des données dans l'interface utilisateur. À l'écran.


En raison de tout ce qui précède, WPF est fondamentalement différent de la plupart des cadres d'interface utilisateur, et supprime ainsi le besoin de tous les horribles passe-partout et hacks qui sont communs dans d'autres cadres,

Je vous suggère de lire tous les liens fournis et de garder à l'esprit tous ces concepts et pratiques lors de la définition de la structure et de l'interface utilisateur d'une application en général.

Faites moi savoir si vous avez besoin d'une aide supplémentaire.

63

Pour autant que je sache, dériver l'interface utilisateur telle qu'elle est, n'est pas disponible sauf pour les ressources XAML (styles pour ex). La raison est peut-être que le gestionnaire d'interface utilisateur ne peut pas deviner où mettre le code XAML étendu: imaginez l'interface utilisateur de base comme suit:

<UserControl > 
 <Grid>
  <Border/>
 </Grid>
</UserControl>

et l'interface utilisateur dérivée:

<UserControl> 
 <DockPanel>
  <Button/>
 </DockPanel>
</UserControl>

Quel serait le résultat après le mix?

une réponse simple pourrait être:

  <UserControl > 
    <Grid>
     <Border/>
    </Grid>
    <DockPanel>
     <Button/>
    </DockPanel>
  </UserControl>

ce qui n'est pas possible.

Qu'en est-il des problèmes d'intégration/de compatibilité des contrôles?

0
HichemSeeSharp