2010-12-10 32 views
2

Ai-je manqué quelque chose dans le passage de .net 3.5 à .net 4 parce que je vois un comportement apparemment buggé qui semble contraire à l'objectif du système. J'essaie de créer une simple bibliothèque MVVM pour travailler en utilisant quelques exemples. Je le consomme dans une application client Twitter pour un apprentissage supplémentaire et j'ai rencontré une grosse pierre d'achoppement.WPF staticresource références aux ressources logiques dans DataTemplates ne résolvant pas à l'exécution

Le scénario est le suivant. Mon objet racine ViewModel (TwitterClientViewModel) reçoit une instance d'un objet DialogViewModel à afficher. Le DialogViewModel est ajouté à une collection et un bool HasDialogs est défini sur true. Les événements PropertyChanged sont appelés pour la collection et l'indicateur si nécessaire. Cette partie fonctionne fabuleusement.

La vue de TwitterClientViewModel s'appelle TwitterClientTemplate et rend visible une superposition pour l'hébergement DialogViewTemplate (vue de DialogViewModel). Le modèle ContentControl d'hébergement fait référence à DialogViewTemplate avec une extension DynamicResource. Cela s'affiche très bien dans le concepteur et à l'exécution.

C'est là que les choses deviennent étranges. Le 'corps' de DialogViewTemplate héberge le contenu de la boîte de dialogue avec un contrôle de contenu supplémentaire lié à DialogViewModel.Content (objet type). L'espoir était qu'avec l'utilisation d'un TemplateSelector (dont j'ai écrit un bon déclaratif, mais que j'ai commenté à des fins de test), je pouvais afficher à la fois du texte et des éléments interactifs. Par exemple, demander des détails à l'utilisateur lors de l'authentification d'un compte Twitter. Dans ce cas, un code PIN.

À ce stade, j'ai deux commandes de contenu imbriquées pour l'implémentation du dialogue. À des fins de test, contentcontrol dans le corps de DialogViewTemplate utilise une extension staticresource pour récupérer EnterPINDialogTemplate (vue pour EnterPINDialogViewModel). EnterPINDialogTemplate et DialogViewTemplate sont tous les deux dans le même fichier (le premier est défini en premier) bien qu'initialement ils étaient séparés.

Lors de l'exécution, l'extension staticresource lève une exception XamlParseException avec le message; 'Fournir une valeur sur' System.Windows.Markup.StaticResourceHolder 'a généré une exception.'

et un message d'exception interne;

'Impossible de trouver la ressource' EnterPINDialogTemplate '. Les noms de ressources sont sensibles à la casse '

L'utilisation d'une ressource dynamic renvoie null et affiche le nom complet du type EnterPINDialogViewModel dans contentcontrol, comme prévu lorsque la ressource n'est pas résolue. Rompre dans mon TemplateSelector personnalisé en appelant FrameWorkElement.FindResource() renvoie une exception similaire (TryFindResource renvoie null). Ma première pensée était que l'arbre logique est divisé lorsque les modèles de données sont construits et je me suis souvenu d'un problème dans cette zone d'un projet antérieur. J'ai essayé d'utiliser la propriété MergeDictionaries de ResourceDictionary pour rendre les dictionnaires ressources avaliable à partir du DataTemplate, mais le concepteur ne m'a pas plu un peu, et l'erreur est décrite ici: http://connect.microsoft.com/VisualStudio/feedback/details/498844/wpf-designer-throws-invalidcastexception

Scratch cette idée. J'ai essayé de fusionner les dictionnaires aux niveaux Application, Window et TwitterClientTemplate, mais je n'ai pas eu de chance.

Voici les fichiers xaml.

DialogTemplates.XAML

<ResourceDictionary 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
xmlns:VM="clr-namespace:EpicTweet.ViewModel" 
xmlns:ET="clr-namespace:EpicTweet" 
xmlns:T="clr-namespace:EpicTweet.Tools" 
xmlns:MV="clr-namespace:MVVM;assembly=MVVM" 
xmlns:Loc="clr-namespace:EpicTweet.Localization" 
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> 
<DataTemplate DataType="VM:EnterPINDialogViewModel" x:Key="EnterPINDialogTemplate"> 
    <Grid d:DesignWidth="453.89" d:DesignHeight="78.92" Loc:ResXManagerProperty.ResourceManager="{x:Static ET:Language.ResourceManager}"> 
     <Grid.RowDefinitions> 
      <RowDefinition Height="Auto"/> 
      <RowDefinition/> 
     </Grid.RowDefinitions> 
     <Grid.ColumnDefinitions> 
      <ColumnDefinition Width="Auto"/> 
      <ColumnDefinition/> 
     </Grid.ColumnDefinitions> 
     <Label Content="{Loc:ResxExtension ResourceName=String_PIN, FallbackValue='&lt;PIN&gt;'}"/> 
     <TextBox Grid.Column="1"/> 
     <TextBlock Grid.Row="1" Grid.RowSpan="2"></TextBlock> 
    </Grid> 
</DataTemplate> 
<DataTemplate x:Key="DialogViewTemplate" DataType="MV:DialogViewModel"> 
    <Border BorderBrush="Black" BorderThickness="1"> 
     <Grid d:DesignWidth="277.419" d:DesignHeight="74.96" Background="{DynamicResource {x:Static SystemColors.ControlDarkBrushKey}}" Height="Auto" Width="Auto"> 
      <Grid.RowDefinitions> 
       <RowDefinition Height="Auto"/> 
       <RowDefinition/> 
       <RowDefinition Height="Auto"/> 
      </Grid.RowDefinitions> 
      <Border d:LayoutOverrides="Width, Height" BorderThickness="0,0,0,1" BorderBrush="Black"> 
       <Label Content="{Binding DisplayName, FallbackValue=Header}" VerticalAlignment="Center" HorizontalAlignment="Left"/>  
      </Border> 
      <ContentControl Content="{Binding Content, FallbackValue=Body}" ContentTemplate="{StaticResource EnterPINDialogTemplate}" HorizontalAlignment="Stretch" d:LayoutOverrides="Height" Grid.Row="1" Margin="5"> 
       <!--<ContentControl.ContentTemplateSelector> 
        <T:TypeTemplateSelector> 
         <T:TemplateTypeRelationship Type="{x:Type VM:EnterPINDialogViewModel}" ResourceKey="EnterPINDialogTemplate"/> 
        </T:TypeTemplateSelector> 
       </ContentControl.ContentTemplateSelector>--> 
      </ContentControl> 
       <ItemsControl Grid.Row="2" Margin="10" 
       ItemsSource="{Binding Commands, Mode=OneTime, FallbackValue={x:Static VM:TwitterClientViewModel.DEFAULT_DIALOG_COMMANDS}}"> 
       <ItemsControl.ItemTemplate> 
        <DataTemplate> 
         <Button 
         Content="{Binding DisplayName, FallbackValue=CommandName, Mode=OneWay}" 
         Command="{Binding}"/> 
        </DataTemplate> 
       </ItemsControl.ItemTemplate> 
       <ItemsControl.ItemsPanel> 
        <ItemsPanelTemplate> 
         <WrapPanel Orientation="Horizontal"/> 
        </ItemsPanelTemplate> 
       </ItemsControl.ItemsPanel> 
      </ItemsControl> 
     </Grid> 
    </Border> 
</DataTemplate> 

TwitterClientDataTemplate.xaml

<ResourceDictionary 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
xmlns:VM="clr-namespace:EpicTweet.ViewModel" 
xmlns:ET="clr-namespace:EpicTweet" 
xmlns:MV="clr-namespace:MVVM;assembly=MVVM" 
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> 
<ResourceDictionary.MergedDictionaries> 
    <ResourceDictionary Source="DialogTemplates.xaml"/> 
</ResourceDictionary.MergedDictionaries> 
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/> 
<DataTemplate x:Key="TwitterClientTemplate" DataType="MV:TwitterClientViewModel"> 
    <ScrollViewer d:DesignWidth="285.083" d:DesignHeight="119.96"> 
     <Grid> 
      <StackPanel d:LayoutOverrides="Width, Height"> 
       <StackPanel Orientation="Horizontal"> 
        <Button Command="{Binding AddAccountCommand.Command}" Content="{Binding AddAccountCommand.DisplayName, FallbackValue=&lt;Add Account&gt;}"/> 
       </StackPanel> 
       <ContentControl/> 
      </StackPanel> 
      <Border BorderThickness="1" Background="#80000000" Visibility="{Binding HasDialogs, Converter={StaticResource BooleanToVisibilityConverter}, FallbackValue=Collapsed, Mode=OneWay}"> 
       <Grid VerticalAlignment="Stretch" MinWidth="50" MaxWidth="200"> 
        <ContentControl Content="{Binding Dialogs[0], Mode=OneWay}" ContentTemplate="{DynamicResource DialogViewTemplate}" HorizontalAlignment="Center" VerticalAlignment="Center"/> 
       </Grid> 
      </Border> 
     </Grid> 
    </ScrollViewer> 
</DataTemplate> 

Aidez-moi stackoverflow, tu es mon seul espoir!

EDIT: Nous avons continué à travailler sur ce problème. Si les deux modèles se trouvent dans le même fichier, les extensions dynamicresource et staticresource résolvent la ressource sans problème. Si elles sont dans des fichiers séparés, la ressource ne se résoudra pas indépendamment de la façon dont je fusionne les dictionnaires; chaque extension renvoie null.

Évidemment, la solution est de jeter les deux ressources dans le même dictionnaire, mais en ce qui me concerne il s'agit d'un hack et n'est pas un comportement prévu du système de recherche de ressources logiques. Je ne suis pas un lapin heureux en ce moment. Cela semble assez non documenté ...

Répondre

7

Si jamais il y avait un pratt, c'est moi. Après 4 heures un vendredi soir essayant de résoudre celui-ci je l'ai craqué, non merci à ce que je peux seulement appeler des rapports d'erreur flaky.

Voici le buzz.

<DataTemplate x:Key="TwitterClientTemplate" DataType="MV:TwitterClientViewModel"> 

devrait être

<DataTemplate x:Key="TwitterClientTemplate" DataType="{x:Type MV:TwitterClientViewModel}"> 

Et bam, cela fonctionne.

Mon gros reproche reste cependant. Pourquoi la syntaxe incorrecte fonctionne-t-elle dans le concepteur mais pas lors de l'exécution? Ma conjecture est parce que l'optimisation de l'exécution ne prend pas la peine de remplir un dictionnaire avec xaml mal écrit mais il serait bon d'obtenir un avertissement que c'est faux.