none
Accéder aux éléments d'un DataTemplate depuis le code behind? RRS feed

  • Question

  • Bonjour a tous,

    j'essaye petit a petit de me faire a WPF et a cette cohabitation XAML/C# mais j'avoue que le concept reste encore difficile a saisir. je cherche actuellement a faire des modifications dynamique sur des items d'un gridview (bindé a une collections d'objets) ainsi qu'au DataTemplate de ce gridview pour faire des opérations de type:

    - redimentionner tous les éléments de mon gridview en modifiant le la grid de mon DataTemplate (height/width)
    - sélectionner une des propriété d'un selectedItem de mon gridview (la value de mon textblock, ou encore la source de mon controle image dans le datatemplate)

    Cependant je ne vois pas du tout comment m'y prendre en C# de manière simple et je ne trouve aucun exemple compréhensible pour le faire, pourriez vous m'aider du coup ^^,

    Ci joint le code de mon DataTemplate

    	<Page.Resources>
            <DataTemplate x:Name="gridViewItemDataTemplate" x:Key="gridViewItemDataTemplate">
    			<Grid x:Name="gridContainer" Width="200" Height="200">
                    <Image x:Name="img" Source="{Binding img}" HorizontalAlignment="Left" Height="200" VerticalAlignment="Top" Width="200" Stretch="UniformToFill"/>
    				<Rectangle Fill="#4C000000" Stroke="Black" Height="34" VerticalAlignment="Bottom"/>
    				<TextBlock x:Name="filename" Margin="0,0,0,4" TextWrapping="Wrap" Text="{Binding filename}" Height="25" VerticalAlignment="Bottom"/>
    				<Border BorderBrush="#FF230303" BorderThickness="1" Margin="0"/>
    			</Grid>
    		</DataTemplate>
    	</Page.Resources>
    Merci d'avance.

    mercredi 26 septembre 2012 20:45

Réponses

  • Merci, grâce à toi je viens de découvrir une petite subtilité de la nouvelle WrapGrid. Il semble qu'elle ne calcule la taille d'une cellule qu'une seule fois lors de l'ajout d'un élément. Au moment ou j'allais te sortir une solution de barbare à grand renfort de VistualTreeHelper ("Pourquoi faire simple quand on peut faire compliqué et que ça marche"), j'ai réalisé qu'en réalité nous avions beaucoup plus simple....

    Voilà ce que je te propose, c'est en plus beaucoup plus performant car il n'y a que deux bindings au lieu d'une tripotée et pas de code-behind :

            <GridView x:Name="gridView" ItemsSource="{Binding SampleItems, ElementName=self}" Margin="50" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
                <GridView.ItemTemplate>
                    <DataTemplate>
                        <Border Background="DarkGray" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
                                <StackPanel >
                                <TextBlock Text="{Binding Value, ElementName=slider}" />
                                <TextBlock Text="{Binding Title}" />
                            </StackPanel>
                        </Border>
                    </DataTemplate>
                </GridView.ItemTemplate>
                <GridView.ItemContainerStyle>
                    <Style TargetType="GridViewItem">
                        <Setter Property="HorizontalAlignment" Value="Stretch" />
                        <Setter Property="VerticalAlignment" Value="Stretch" />
                        <Setter Property="HorizontalContentAlignment" Value="Stretch" />
                        <Setter Property="VerticalContentAlignment" Value="Stretch" />
                    </Style>
                </GridView.ItemContainerStyle>
                <GridView.ItemsPanel>
                    <ItemsPanelTemplate>
                        <WrapGrid ItemHeight="{Binding Value, ElementName=slider}" ItemWidth="{Binding Value, ElementName=slider}" />
                    </ItemsPanelTemplate>
                </GridView.ItemsPanel>
            </GridView>
            <Slider x:Name="slider" Minimum="50" Value="75" Maximum="100" VerticalAlignment="Top"></Slider>

    Toute la magie repose dans les propriété ItemHeight et ItemWidth de la WraGrid et dans la modification du style de l'itemcontainer pour qu'il occupe toute la cellule (passage en stretch). Tu feras cependant attention à un detail : La taille ainsi donnée inclu les marges.

    Je pense que là je viens de mettre fin à tes souffrances :).


    Cyprien Autexier

    vendredi 28 septembre 2012 18:32

Toutes les réponses

  • Il y a peut-être plus propre, mais tu peux ajouter l'évènement Loaded sur la Grid

    <Grid x:Name="gridContainer" Width="200" Height="200" Loaded="Grid_Loaded" >

    Et en code-behind :

    private void Grid_Loaded(object sender, RoutedEventArgs e)
    {
       Grid myGrid = ((Grid)sender);
       myGrid.Height = 150; // Changement de la propriété Height de la grid
       TextBlock myTextBlock = (TextBlock)myGrid.FindName("fileName"); // Récupération du textblock
    }

    Par contre pour quelles raisons tu veux changer la taille de la grid ?


    mercredi 26 septembre 2012 20:57
  • En fait c'est une sorte de test de galeries d'images multi dossier ou l'utilisateur choisi plusieurs dossiers contenant des images et ou tout est regroupé comme si c'était le même dossier dans ce gridview.

    - les items sont triés par nom de fichier, en faisant un zoom out il obtient un gridview avec les lettres de l'alphabet pour voir les images dont le nom commence par la lettre en question

    - quand il sélectionne un item, je dois récupérer la propriété source du controle Image en question pour l'afficher dans un control en plein écran (avec une petite animation) => d’où le besoin de recupérer le parametre de du griditem en question

    - les éléments dans ce gridview font par défaut 200*200px, cependant j'ai un petit slider qui permet de redimensionner tous les items entre 100*100px et 200*200 pour voir plus ou moins d'items sur l'écran en fonction de ce que désire l'utilisateur => d’où le besoin de modifier directement le datatemplate en faisant un truc du genre myGrid.Height = monSlider.Value / idem pour le Width... ceci sur tous les items du gridview

    ---------------------------

    EDIT: pour les éléments dans le selectedItem j'ai trouvé avec le debugger de visual studio, j'ai fais un code de test et ca semble marcher, par contre pour modifier la propriété height/width du datatemplate lorsque le slider change de valeur je ne vois toujours pas

    filePath i = (filePath)imagesGridView.SelectedItem; MessageDialog dlg = new MessageDialog(i.filepath); await dlg.ShowAsync();

    // où filepath est le nom de la classe que j'utilise pour faire ma collection et mon binding



    mercredi 26 septembre 2012 21:57
  • Pour changer le height/width tu peux passer par une resource :

    Tu commences par définir le type de la resource dans une classe comme ceci par exemple :

    public class Size : INotifyPropertyChanged
        {
            private int _height;
            private int _width;
            public int Height
            {
                get
                {
                    return this._height;
                }
                set
                {
                    if(this._height != value)
                    {
                        this._height = value;
                        OnPropertyChanged("Height");
                    }
                }
            }
            public int Width
            {
                get
                {
                    return this._width;
                }
                set
                {
                    if (this._width != value)
                    {
                        this._width = value;
                        OnPropertyChanged("Width");
                    }
                }
            }
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            private void OnPropertyChanged(String info)
            {
                PropertyChangedEventHandler handler = PropertyChanged;
                if (handler != null)
                {
                    handler(this, new PropertyChangedEventArgs(info));
                }
            }
    
        }

    Ensuite tu ajoutes la resource à ta page  :

    <Page.Resources>
       <local:Size x:Key="rectSize" Height="100" Width="100"></local:Size>
    </Page.Resources.Resources>

    Ne pas oublier de rajouter le namespace :

    xmlns:local="TonNamespaceOuEstDeclareSize"

    Dans ton DataTemplate tu bindes sur la Resource :

    <Page.Resources>
            <DataTemplate x:Name="gridViewItemDataTemplate" x:Key="gridViewItemDataTemplate">
    		<Grid x:Name="gridContainer" Width="{Binding Width, Source={StaticResource rectSize}}" Height="{Binding Height, Source={StaticResource rectSize}}">
                                 <Image x:Name="img" Source="{Binding img}" HorizontalAlignment="Left" Height="200" VerticalAlignment="Top" Width="200" Stretch="UniformToFill"/>
    			<Rectangle Fill="#4C000000" Stroke="Black" Height="34" VerticalAlignment="Bottom"/>
    			<TextBlock x:Name="filename" Margin="0,0,0,4" TextWrapping="Wrap" Text="{Binding filename}" Height="25" VerticalAlignment="Bottom"/>
    			<Border BorderBrush="#FF230303" BorderThickness="1" Margin="0"/>
    		</Grid>
    	</DataTemplate>
    </Page.Resources>

    Sur ton Slider tu t'abonnes à l'évènement ValueChanged :

    <Slider x:Name="slid" Maximum="200" Minimum="100" Value="100" ValueChanged="slid_ValueChanged"></Slider>

    Et dans la méthode attaché à l'évènement tu changes les valeurs de la resource :

     private void slid_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
    {
        Slider s = ((Slider)sender);
        Size size = (Size)Resources["rectSize"];
        size.Height = (int)s.Value;
        size.Width = (int)s.Value;
    }

    Normalement ca devrait marcher.

    L'exemple est fait pour comprendre facilement, mais une approche MvvM serait plus propre quand même.


    jeudi 27 septembre 2012 08:31
  • Bonjour,

    J'ai fais toutes les manip en question (effectivement relativement clair a comprendre) cependant il ne me reste qu'un problème au niveau de ma méthode valueChanged ou le deuxieme paramètre n'est pas un " RoutedPropertyChangedEventArgs<double> e " et ne veut d'ailleurs pas l'être quand je le force manuellement

    Erreur    2    Le type ou le nom d'espace de noms 'RoutedPropertyChangedEventArgs' est introuvable (une directive using ou une référence d'assembly est-elle manquante ?)   ...

    Tous les éléments sont dans le même namespace donc il devrait le trouver :\

            private void zoomInSlider_ValueChanged(object sender, Windows.UI.Xaml.Controls.Primitives.RangeBaseValueChangedEventArgs e)
            {

            }

    jeudi 27 septembre 2012 15:31
  • Il faut générer la signature de la méthode a partir du xaml. Supprime la fonction dans le code behind. Et clic droit sur le nom de la fonction dans le xaml pour générer une signature correct. Et recopie le contenu dedans.

    Désolé j'ai fait l'exemple a partir d'un projet silverlight car j'avais que ca sous la main.... Je me rappelais plus de ce changement.


    jeudi 27 septembre 2012 15:49
  • ok visiblement ca avance, le datatemplate est bien modifié dynamiquement en fonction de mon slider, par contre le rendu visuel ne se fais que pour les nouveaux éléments crées après la modif et pas (aussi) sur les items déja présents dans le gridview, comment pourrai-je régler ca?

    https://skydrive.live.com/redir?resid=76FDA2BF6399E396!135

    jeudi 27 septembre 2012 16:01
  • ok visiblement ca avance, le datatemplate est bien modifié dynamiquement en fonction de mon slider, par contre le rendu visuel ne se fais que pour les nouveaux éléments crées après la modif et pas (aussi) sur les items déja présents dans le gridview, comment pourrai-je régler ca?

    https://skydrive.live.com/redir?resid=76FDA2BF6399E396!135

    Bonjour,

    Il est effectivement normal que les modifications appliquées à ton datatemplate ne s'applique qu'au nouveaux éléments créés. En effet et ce n'est pas une surprise, un DataTemplate est en fait un modèle qui sera cloné à chaque fois qu'il sera appliqué. Si tu le modifies entre temps seuls les nouveaux clones (nouveaux éléments) bénéficieront des modifications apportées au modèle.

    Cependant dans ton cas, je n'aurai pas pensé que le clonage irait jusqu'à la size (quoique Size est un ValueType donc ce n'est pas si surprenant). Peux tu essayer de faire un GridView.UpdateLayout() quand tu changes les tailles pour être sûr que ton gridView refresh bien son layout (et prennent bien en compte les nouvelles taille au niveau des ItemContainer)

    Après avoir regarder ton screenshot, je pense que tu peux faire relativement plus simple en utilisant le binding sur élément :

    <GridView x:Name="gridView" ItemsSource="{Binding SampleItems, ElementName=self}" Margin="50">
                <GridView.ItemTemplate>
                    <DataTemplate>
                        <Border Background="Aquamarine">
                            <StackPanel Height="{Binding Value, ElementName=slider}">
                                <TextBlock Text="{Binding Value, ElementName=slider}" />
                                <TextBlock Text="{Binding Title}" />
                            </StackPanel>
                        </Border>
                    </DataTemplate>
                </GridView.ItemTemplate>
            </GridView>
            <Slider x:Name="slider" Minimum="50" Value="75" Maximum="100" VerticalAlignment="Top" ValueChanged="slider_ValueChanged_1"></Slider>

    en code-behind :

    private void slider_ValueChanged_1(object sender, RangeBaseValueChangedEventArgs e)
            {
                if (this.gridView != null)
                    this.gridView.UpdateLayout();
            }

    Ce n'est pas nécessairement ultra clean, mais ça fait la blague :).

    Cyprien Autexier


    • Modifié sandørMVP jeudi 27 septembre 2012 19:04
    jeudi 27 septembre 2012 19:03
  • Bonjour,

    Est-ce que vous avez testé les solutions proposées ? Merci de partager avec nous les résultats, afin que d'autres personnes avec le même problème puissent profiter de cette solution.

    Cordialement,

    Aurel


    Aurel BERA, Microsoft
    Microsoft propose ce service gratuitement, dans le but d'aider les utilisateurs et d'élargir les connaissances générales liées aux produits et technologies Microsoft. Ce contenu est fourni "tel quel" et il n'implique aucune responsabilité de la part de Microsoft.

    vendredi 28 septembre 2012 09:19
  • Malheureusement même le gridView.UpdateLayout() n'y change rien :s, ma seule solution actuelle est d'effacer tous les items du gridview et les recharger a partir de la collection une fois que le DataTemplate est modifié mais cela est plutôt sale et relativement non optimisé notamment si la bibliothèque d'élément compte plusieurs centaines d'images...

    • Marqué comme réponse Aurel Bera vendredi 28 septembre 2012 10:49
    • Non marqué comme réponse Aurel Bera vendredi 28 septembre 2012 10:49
    vendredi 28 septembre 2012 10:38
  • Malheureusement même le gridView.UpdateLayout() n'y change rien :s, ma seule solution actuelle est d'effacer tous les items du gridview et les recharger a partir de la collection une fois que le DataTemplate est modifié mais cela est plutôt sale et relativement non optimisé notamment si la bibliothèque d'élément compte plusieurs centaines d'images...


    As tu essayé la méthode alternative que je te propose car chez moi elle fonctionne sans encombre ?

    Cyprien Autexier

    vendredi 28 septembre 2012 11:06
  • Effectivement après de nombreux tests je n'avais pas testé cette alternative, uniquement celui de l'updateLayout() et a mon grand tort vu que ca fonctionne :), il ne reste plus qu'un petit problème qui j'espère ne sera pas trop dur a régler:

    Par défaut mes items sont en 200*200px et peuvent être réduits jusqu'en 100*100, cependant une fois réduits ils occupent toujours la même place qu'avant au lieu de se replacer dynamiquement, par exemple quand j'ai 2 items par colonne en "grande taille", une fois réduits j'en ai toujours 2 en petite taille alors que l'idéal serait d'en avoir 4... Une idée?

    Merci d'avance :)

    https://skydrive.live.com/redir?resid=76FDA2BF6399E396!135

    vendredi 28 septembre 2012 15:58
  • Merci, grâce à toi je viens de découvrir une petite subtilité de la nouvelle WrapGrid. Il semble qu'elle ne calcule la taille d'une cellule qu'une seule fois lors de l'ajout d'un élément. Au moment ou j'allais te sortir une solution de barbare à grand renfort de VistualTreeHelper ("Pourquoi faire simple quand on peut faire compliqué et que ça marche"), j'ai réalisé qu'en réalité nous avions beaucoup plus simple....

    Voilà ce que je te propose, c'est en plus beaucoup plus performant car il n'y a que deux bindings au lieu d'une tripotée et pas de code-behind :

            <GridView x:Name="gridView" ItemsSource="{Binding SampleItems, ElementName=self}" Margin="50" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
                <GridView.ItemTemplate>
                    <DataTemplate>
                        <Border Background="DarkGray" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
                                <StackPanel >
                                <TextBlock Text="{Binding Value, ElementName=slider}" />
                                <TextBlock Text="{Binding Title}" />
                            </StackPanel>
                        </Border>
                    </DataTemplate>
                </GridView.ItemTemplate>
                <GridView.ItemContainerStyle>
                    <Style TargetType="GridViewItem">
                        <Setter Property="HorizontalAlignment" Value="Stretch" />
                        <Setter Property="VerticalAlignment" Value="Stretch" />
                        <Setter Property="HorizontalContentAlignment" Value="Stretch" />
                        <Setter Property="VerticalContentAlignment" Value="Stretch" />
                    </Style>
                </GridView.ItemContainerStyle>
                <GridView.ItemsPanel>
                    <ItemsPanelTemplate>
                        <WrapGrid ItemHeight="{Binding Value, ElementName=slider}" ItemWidth="{Binding Value, ElementName=slider}" />
                    </ItemsPanelTemplate>
                </GridView.ItemsPanel>
            </GridView>
            <Slider x:Name="slider" Minimum="50" Value="75" Maximum="100" VerticalAlignment="Top"></Slider>

    Toute la magie repose dans les propriété ItemHeight et ItemWidth de la WraGrid et dans la modification du style de l'itemcontainer pour qu'il occupe toute la cellule (passage en stretch). Tu feras cependant attention à un detail : La taille ainsi donnée inclu les marges.

    Je pense que là je viens de mettre fin à tes souffrances :).


    Cyprien Autexier

    vendredi 28 septembre 2012 18:32
  • Effectivement ca fonctionne parfaitement bien, merci beaucoup pour tes idées et ta patience ;)
    vendredi 28 septembre 2012 19:00