[VB/WPF] Sélection dynamique du Datatemplate

Jayme65 Messages postés 66 Date d'inscription lundi 23 avril 2007 Statut Membre Dernière intervention 26 mars 2019 - 7 sept. 2016 à 10:47
Bonjour,

Je dispose d'une liste de noms (mais ici, ce sera "1", "2", "3",...) et pour certains, d'une image correspondante ("1png", "3.png",...)

Selon que j'image correspondante soit disponible ou pas, je voulais pouvoir afficher dans un control (et pas nécessairement une liste) l'image OU le texte!

J'ai penser à un ItemsControl avec un DataTemplate et un DataTemplateSelector et suis parvenu à ce résultat:
<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApplication1"
    Title="MainWindow" Height="350" Width="525" Background="#FF3A3A48">
    <Grid Name="mainGrid">
        <Grid.Resources>
            <local:RelativeToAbsolutePathConverter x:Key="relToAbsPathConverter" />
            <DataTemplate x:Key="stringTemplate">
                <TextBlock Text="{Binding}" HorizontalAlignment="Center" Foreground="Beige"/>
            </DataTemplate>
            <DataTemplate x:Key="imageTemplate">
                <Image Source="{Binding Converter={StaticResource relToAbsPathConverter}}"/>
            </DataTemplate>
            <local:ImgStringTemplateSelector ImageTemplate="{StaticResource imageTemplate}" StringTemplate="{StaticResource stringTemplate}" x:Key="imgStringTemplateSelector" />
        </Grid.Resources>
        <ItemsControl  ScrollViewer.CanContentScroll="False" Name="ItemsControl1" ItemTemplateSelector="{StaticResource imgStringTemplateSelector}" Margin="0,0,134,0">
            <ItemsControl.ItemContainerStyle>
                <Style>
                    <Setter Property="Control.Height" Value="25"/>
                    <Setter Property="Control.Margin" Value="4"/>
                </Style>
            </ItemsControl.ItemContainerStyle>
        </ItemsControl>
        <Button Content="Up" Height="23" HorizontalAlignment="Left" Margin="399,244,0,0" Name="Button1" VerticalAlignment="Top" Width="75" />
    </Grid>
</Window>

Class MainWindow
    Dim displayedItems As Integer = 9
    Dim displayed As New List(Of logos)
 
    Private Sub MainWindow_Loaded(sender As Object, e As System.Windows.RoutedEventArgs) Handles Me.Loaded
        ' Create List
        displayed.Add(New logos With {.Name = "1"})
        displayed.Add(New logos With {.Name = "2"})
        displayed.Add(New logos With {.Name = "3"})
        displayed.Add(New logos With {.Name = "4"})
        displayed.Add(New logos With {.Name = "5"})
        displayed.Add(New logos With {.Name = "6"})
        displayed.Add(New logos With {.Name = "7"})
        displayed.Add(New logos With {.Name = "8"})
        displayed.Add(New logos With {.Name = "9"})
        displayed.Add(New logos With {.Name = "10"})
        displayed.Add(New logos With {.Name = "11"})
        displayed.Add(New logos With {.Name = "12"})
        displayed.Add(New logos With {.Name = "13"})
        displayed.Add(New logos With {.Name = "14"})
        displayed.Add(New logos With {.Name = "15"})
        displayed.Add(New logos With {.Name = "16"})
 
        ' Populate ItemsControl
        For i As Integer = 0 To displayedItems - 1
            ItemsControl1.Items.Add(displayed(i).Name)
        Next
    End Sub
 
    Private Sub Button1_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles Button1.Click
        For i As Integer = 0 To displayedItems - 1
            ItemsControl1.Items(i) = displayed(i + 1).Name
        Next
    End Sub
End Class
 
Public Class logos
    Private _name As String
    Public Property Name() As String
        Get
            Return _name
        End Get
        Set(value As String)
            _name = value
        End Set
    End Property
End Class
 
Public Class RelativeToAbsolutePathConverter
    Implements IValueConverter
    Public Function Convert(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements IValueConverter.Convert
        Dim relative As [String] = TryCast(value, String)
        If relative Is Nothing Then
            Return Nothing
        End If
        Return System.AppDomain.CurrentDomain.BaseDirectory & "Logos\" & relative & ".png"
    End Function
    Public Function ConvertBack(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements IValueConverter.ConvertBack
        Throw New NotImplementedException()
    End Function
End Class
 
Public Class ImgStringTemplateSelector
    Inherits DataTemplateSelector
    Public Property ImageTemplate() As DataTemplate
        Get
            Return m_ImageTemplate
        End Get
        Set(value As DataTemplate)
            m_ImageTemplate = value
        End Set
    End Property
    Private m_ImageTemplate As DataTemplate
    Public Property StringTemplate() As DataTemplate
        Get
            Return m_StringTemplate
        End Get
        Set(value As DataTemplate)
            m_StringTemplate = value
        End Set
    End Property
    Private m_StringTemplate As DataTemplate
 
    Public Overrides Function SelectTemplate(item As Object, container As DependencyObject) As DataTemplate
        Dim path As String = DirectCast(item, String)
        If System.IO.File.Exists(System.AppDomain.CurrentDomain.BaseDirectory & "Logos\" & path & ".png") Then
            Return ImageTemplate
        End If
        Return StringTemplate
    End Function
End Class


Donc, j'envoie du texte à l'ItemsControl et si il y a une image disponible avec ce nom il affiche l'image, sinon il affiche le texte!

Seulement j'ai un problème: quand je veux modifier le contenu des Items de l'ItemsControl (par l'appui sur le bouton) j'ai l'erreur suivante:

System.Windows.Data Error: 6 : 'DynamicValueConverter' converter failed to convert value 'C:\Users\jmd_000\Desktop\Demande forum\TestGridDisplayedItem\bin\Debug\Logos\4.png' (type 'String'); fallback value will be used, if available. BindingExpression:Path=; DataItem='String' (HashCode=-842352756); target element is 'Image' (Name=''); target property is 'Source' (type 'ImageSource') FileNotFoundException:'System.IO.FileNotFoundException: Le fichier 'C:\Users\jmd_000\Desktop\Demande forum\TestGridDisplayedItem\bin\Debug\Logos\4.png' est introuvable.
Nom de fichier : 'C:\Users\jmd_000\Desktop\Demande forum\TestGridDisplayedItem\bin\Debug\Logos\4.png'

...contrairement à ce que j'imaginais il n'y a pas d'update du DataTemplateSelector et l'application se plaint de ne pas trouver d'image pour un item dont le DataTemplate est toujours une image, alors qu'il devrait être du texte! (j'espère que je suis clair dans mes explication!!? ;-) )

J'aurais donc besoin de votre aide sur ces points:

1) Si je reste sur cette logique, comment puis-je forcer la ré-évaluation par le DataTemplateSelector qu'il doit appeler un template texte ou image? (trigger?)

2) En réalité, je n'ai pas du tout besoin d'une 'liste'..dans l'idéal les objets devraient être disséminés sur la page et je préfèrerais avoir des ContentControl qui se comporteraient de la même façon, c'est à dire adapter leur template selon que l'image soit disponible ou pas. Mais comme les objets doivent impérativement être créés dynamiquement, j'ignore comment attacher un DataTemplateSelector (et ses DataTemplate) par le code uniquement! Pourriez-vous m'aider pour cela? (Et le problème de la ré-évaluation par le DataTemplateSelector se poserait-il à nouveau?)

3) Globalement, quelle est la meilleur façon de créer un conteneur, qui peut recevoir texte ou image et adapter sa présentation en fonction, et être mis-à jour?

Un grand merci pour m'avoir lu et pour l'aide que vous voudrez bien m'apporter!!

Voici le lien vers le projet VS
http://www87.zippyshare.com/v/Ai28XH5A/file.html