2009-12-20 14 views
7

Je crée un contrôle personnalisé WPF, un Button avec un Image et un Text. J'ai ajouté deux propriétés de dépendance au contrôle, ImagePath et Text, et le modèle de contrôle (dans Thèmes \ Generic.xaml) est un panneau de pile simple qui organise l'image et le texte horizontalement.Contrôle personnalisé WPF: TemplateBinding to Image

La propriété Text fonctionne correctement. Mais pour une raison quelconque, l'image d'exemple dans mon projet de test n'apparaît pas lorsque j'utilise TemplateBinding à la propriété de dépendance ImagePath pour obtenir son chemin. J'ai testé l'image en remplaçant temporairement le TemplateBinding dans le contrôle personnalisé avec un chemin d'accès à l'image, auquel cas il apparaît.

J'espère que quelqu'un avec plus d'expérience dans ce domaine peut jeter un coup d'oeil et me dire pourquoi le contrôle ne fonctionne pas comme prévu. Merci de votre aide.

Ma solution VS VS 2008 contient un projet, CustomControlDemo. Le projet contient un contrôle personnalisé, TaskButton.cs et une fenêtre principale, Window1.xaml, que j'utilise pour tester le contrôle. Mon image de test, calendar.png, se trouve dans un dossier Resources au niveau racine du projet et Generic.xaml se trouve dans un dossier Themes, également au niveau racine du projet.

Voici le code de mon contrôle personnalisé (de TaskButton.cs):

using System.Windows; 
using System.Windows.Controls; 

namespace CustomControlDemo 
{ 
    public class TaskButton : RadioButton 
    { 
     #region Fields 

     // Dependency property backing variables 
     public static readonly DependencyProperty ImagePathProperty; 
     public static readonly DependencyProperty TextProperty; 

     #endregion 

     #region Constructors 

     /// <summary> 
     /// Default constructor. 
     /// </summary> 
     static TaskButton() 
     { 
      DefaultStyleKeyProperty.OverrideMetadata(typeof(TaskButton), new FrameworkPropertyMetadata(typeof(TaskButton))); 

      // Initialize ImagePath dependency properties 
      ImagePathProperty = DependencyProperty.Register("ImagePath", typeof(string), typeof(TaskButton), new UIPropertyMetadata(null)); 
      TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(TaskButton), new UIPropertyMetadata(null)); 
     } 

     #endregion 

     #region Dependency Property Wrappers 

     /// <summary> 
     /// The ImagePath dependency property. 
     /// </summary> 
     public string ImagePath 
     { 
      get { return (string)GetValue(ImagePathProperty); } 
      set { SetValue(ImagePathProperty, value); } 
     } 

     /// <summary> 
     /// The Text dependency property. 
     /// </summary> 
     public string Text 
     { 
      get { return (string)GetValue(TextProperty); } 
      set { SetValue(TextProperty, value); } 
     } 

     #endregion 
    } 
} 

Et voici le modèle de contrôle (de generic.xaml):

<ResourceDictionary 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="clr-namespace:CustomControlDemo"> 


    <Style TargetType="{x:Type local:TaskButton}"> 
     <Setter Property="Template"> 
      <Setter.Value> 
       <ControlTemplate TargetType="{x:Type local:TaskButton}"> 
        <StackPanel Height="Auto" Orientation="Horizontal"> 
         <Image Source="{TemplateBinding ImagePath}" Width="24" Height="24" Stretch="Fill"/> 
         <TextBlock Text="{TemplateBinding Text}" HorizontalAlignment="Left" Foreground="{DynamicResource TaskButtonTextBrush}" FontWeight="Bold" Margin="5,0,0,0" VerticalAlignment="Center" FontSize="12" /> 
        </StackPanel> 
       </ControlTemplate> 
      </Setter.Value> 
     </Setter> 
    </Style> 
</ResourceDictionary> 

Et enfin, ici est le balisage Window1 que j'utilise pour tester le contrôle:

<Window x:Class="CustomControlDemo.Window1" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:customControl="clr-namespace:CustomControlDemo" 
    Title="Window1" Height="300" Width="300"> 
    <Grid> 
     <customControl:TaskButton ImagePath="Resources\calendar.png" Text="Calendar" /> 
    </Grid> 
</Window> 

Des idées pour lesquelles le chemin de l'image ne fonctionne pas Roi? Merci encore.

Répondre

4

L'image ne prend pas de chaîne comme source :) Vous pouvez le voir dans intellisense. Vous devez lier sur un ImageSource (ou utiliser un IValueConverter pour convertir la chaîne en ImageSource)

Voir la question this pour obtenir quelques conseils sur la façon de faire cette conversion.

9

Je vais laisser la réponse de cwap comme réponse acceptée, parce qu'elle est techniquement correcte. Cependant, il s'avère qu'il existe un moyen plus simple de résoudre ce problème. TemplateBindings ne sont pas des objets Binding de première classe. Ils sont conçus pour être légers, donc ils sont à sens unique, et ils manquent de certaines caractéristiques d'autres objets Binding. Plus particulièrement, ils ne prennent pas en charge les convertisseurs de type connus associés à une cible. Voir MacDonald, Pro WPF dans C# 2008, p. 872. C'est pourquoi cwap répond correctement que je devrais probablement créer un convertisseur de type et le référencer spécifiquement dans le modèle de contrôle pour mon bouton personnalisé.

Mais je n'ai pas besoin d'utiliser un TemplateBinding pour lier le modèle de contrôle à la propriété ImagePath de mon contrôle personnalisé. Je peux utiliser un objet Binding simple.Voici le balisage révisé pour le modèle de mon contrôle personnalisé:

<!-- Task Button Default Control Template--> 
<Style TargetType="{x:Type local:TaskButton}"> 
    <Setter Property="Template"> 
     <Setter.Value> 
      <ControlTemplate TargetType="{x:Type local:TaskButton}"> 
       <StackPanel Height="Auto" Orientation="Horizontal"> 
        <Image Source="{Binding Path=ImagePath, RelativeSource={RelativeSource TemplatedParent}}" Width="24" Height="24" Stretch="Fill" Margin="10,0,0,0" /> 
        <TextBlock Text="{TemplateBinding Text}" HorizontalAlignment="Left" Foreground="{TemplateBinding Foreground}" FontWeight="Bold" Margin="5,0,10,0" VerticalAlignment="Center" FontSize="12" /> 
       </StackPanel> 
      </ControlTemplate> 
     </Setter.Value> 
    </Setter> 
</Style> 

Si vous regardez le ImageControl dans le modèle, vous pouvez voir le changement. Notez la propriété RelativeSource dans le même objet. Définir cette propriété à = {RelativeSource TemplatedParent} est ce qui me permet d'entrer un chemin relatif dans mon instance Window1 du TaskButton et de le résoudre correctement dans le contrôle personnalisé. Donc, ma recommandation pour d'autres personnes recherchant ce thread serait de passer le convertisseur de valeur et de passer simplement de TemplateBinding à Binding pour la propriété Image.

Merci également à Marco Zhou, qui a fourni this answer à une question similaire dans le forum MSDN WPF.

2

En fait, aucune de ces réponses n'est correcte.

{TemplateBinding ImagePath} est rien de plus qu'un raccourci pour {Binding Path=ImagePath, RelativeSource={RelativeSource TemplatedParent}} et en tant que tel est presque complètement identique.

De plus, si vous fournissez une chaîne pour ImagePath, celle-ci se résoudra correctement en ImageSource, mais les performances de votre application s'en ressentiront. Le vrai problème a à voir avec le chemin de l'image relative et absolue sur le ImagePath="Resources\calendar.png" fourni dans le xaml pour le test. Cela indique au compilateur que le chemin fourni est absolu en raison de l'utilisation de \ au lieu de/dans la définition du chemin. La raison pour laquelle la forme longue de la liaison fonctionne et le raccourci n'est pas qu'elle fournit des indices au compilateur que la source de l'image fournie (Resources \ calendar.png) est un chemin relatif et non un chemin absolu , donc l'image est trouvée et la liaison fonctionne. Si vous déboguez la liaison, vous verrez que le raccourci essaie de résoudre la chaîne fournie dans une source d'image mais ne peut pas trouver le fichier "Resources \ calendar.png" Si vous fournissez un URI complet à l'image, par exemple "C:\...\Resources\calendar.png""/application;component/Resources/calendar.png" alors l'image sera trouvée et la liaison sera résolue. Ce point devient très important lorsque vous essayez de référencer des images à partir d'une source externe au lieu de celles qui ont été compilées en tant que ressources dans la compilation finale.

0

manière simple (testé) 1-faire votre valueConverter comme celui-ci

public class objectToImageSourceConverter:IValueConverter 
    { 

     public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
     { 

      string packUri =value.ToString(); 
      ImageSource Source = new ImageSourceConverter().ConvertFromString(packUri) as ImageSource; 
      return Source; 
     } 

     public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
     { 
      throw new NotImplementedException(); 
     } 
    } 

2-lier votre image Source au properety chaîne de parent (i utilisé propriété "tag") comme celui-ci XAML:

<Image HorizontalAlignment="Right" Height="Auto" Margin="0,11.75,5.5,10.75" VerticalAlignment="Stretch" Width="40.997" Source="{Binding Path=Tag, RelativeSource={RelativeSource TemplatedParent}}"/>