Laden...

XAML verschiedenes DataTemplate je nach Datentyp

Erstellt von TripleX vor 14 Jahren Letzter Beitrag vor 14 Jahren 1.715 Views
TripleX Themenstarter:in
328 Beiträge seit 2006
vor 14 Jahren
XAML verschiedenes DataTemplate je nach Datentyp

hallo Gemeinde,

ich habe gerade ein Probem welches ich zwar schon gelöst habe, aber mir gefällt dass so nicht wie ich es gelöst habe. Deswegen wollte ichmal frgane ob es da keine bessere Lösung für das Problem gibt.

Also in meinem ViewModel habe ich eine ObservableCollection mit Einträgen vom Typ Message. Dies ist eine abstrakte Klasse und es hat 3 abgeleitete Klassen: RealtimeMessage, CommandMessage und UnknowMessage. Die ersten beiden haben eine Property namens Typ (ist ein Enum), wo der Typ der Nachricht nochmal spezifiziert ist.

Jetzt wollte ich diese nachrichten in eine Listbox ausgeben, und je nach Klasse und Typ soll der Text der ausgegeben werden soll, eine andere Farbe haben.

also ich habe es folgendermaßen gelöst: Ich habe einen TemplateSelector gebastelt welcher mir je nach Klassentyp und Typ-Feld (Property) das jeweilige Template lädt:


    internal class MessageListBoxItemTemplateSelector : DataTemplateSelector
    {
        public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
        {
            Message obj = item as Message;
            ContentPresenter pres = container as ContentPresenter;
            DataTemplate dataTemplate;

            if (obj is RealtimeMessage)
            {
                RealtimeMessage m = (obj as RealtimeMessage);
                switch (m.Type)
                {
                    case RealtimeMessageType.Bytecount:
                        dataTemplate = pres.FindResource("RealTimeMessage_Bytecount_Template") as DataTemplate;
                        break;
                    case RealtimeMessageType.Fatal:
                        dataTemplate = pres.FindResource("RealTimeMessage_Fatal_Template") as DataTemplate;
                        break;
                    case RealtimeMessageType.Log:
                        if (m.Text.Contains("Success") || m.Text.Contains("Sequence Complete"))
                            dataTemplate = pres.FindResource("RealTimeMessage_Log_Success_Template") as DataTemplate;
                        else if (m.Text.Contains(",F,") || m.Text.Contains(",W,"))
                            dataTemplate = pres.FindResource("RealTimeMessage_Log_Error_Template") as DataTemplate;
                        else
                            dataTemplate = pres.FindResource("RealTimeMessage_Log_Template") as DataTemplate;
                        break;
                    case RealtimeMessageType.State:
                        if (m.Text.Contains("CONNECTED"))
                            dataTemplate = pres.FindResource("RealTimeMessage_State_Connected_Template") as DataTemplate;
                        else 
                            dataTemplate = pres.FindResource("RealTimeMessage_State_Template") as DataTemplate;
                        break;
                    default:
                        dataTemplate = pres.FindResource("RealTimeMessageTemplate") as DataTemplate;
                        break;
                }
            }
            else if (obj is CommandMessage)
            {
                switch ((obj as CommandMessage).Type)
                {
                    case CommandMessageType.Success:
                        dataTemplate = pres.FindResource("CommandMessage_Success_Template") as DataTemplate;
                        break;
                    case CommandMessageType.Error:
                        dataTemplate = pres.FindResource("CommandMessage_Error_Template") as DataTemplate;
                        break;
                    case CommandMessageType.End:
                        dataTemplate = pres.FindResource("CommandMessage_End_Template") as DataTemplate;
                        break;
                    default:
                        dataTemplate = pres.FindResource("CommandMessageTemplate") as DataTemplate;
                        break;
                }
            }
            else
                dataTemplate = pres.FindResource("UnknownMessageTemplate") as DataTemplate;

            return dataTemplate;

        }
    }

im xaml code habe ich dann alle Templates geschrieben:


	<UserControl.Resources>
		<local:MessageListBoxItemTemplateSelector x:Key="MessageListBoxItemTemplateSelector" />
				
		<DataTemplate x:Key="RealTimeMessageTemplate">
		    <TextBlock Text="{Binding Text}" Foreground="Gray"/>
		</DataTemplate>			
			<DataTemplate x:Key="RealTimeMessage_Bytecount_Template">
			    <TextBlock Text="{Binding Text}" Foreground="Red"/>
			</DataTemplate>
			<DataTemplate x:Key="RealTimeMessage_Fatal_Template">
			    <TextBlock Text="{Binding Text}" Foreground="Red"/>
			</DataTemplate>
			<DataTemplate x:Key="RealTimeMessage_Log_Template">
			    <TextBlock Text="{Binding Text}" Foreground="Gray"/>
			</DataTemplate>
				<DataTemplate x:Key="RealTimeMessage_Log_Success_Template">
				    <TextBlock Text="{Binding Text}" Foreground="Green"/>
				</DataTemplate>
				<DataTemplate x:Key="RealTimeMessage_Log_Error_Template">
				    <TextBlock Text="{Binding Text}" Foreground="Red"/>
				</DataTemplate>
			<DataTemplate x:Key="RealTimeMessage_State_Template">
			    <TextBlock Text="{Binding Text}" Foreground="Blue"/>
			</DataTemplate>	
				<DataTemplate x:Key="RealTimeMessage_State_Connected_Template">
				    <TextBlock Text="{Binding Text}" Foreground="Green"/>
				</DataTemplate>
		<DataTemplate x:Key="CommandMessageTemplate">
		    <TextBlock Text="{Binding Text}" Foreground="Black"/>
		</DataTemplate>
			<DataTemplate x:Key="CommandMessage_Success_Template">
			    <TextBlock Text="{Binding Text}" Foreground="Green"/>
			</DataTemplate>
			<DataTemplate x:Key="CommandMessage_Error_Template">
			    <TextBlock Text="{Binding Text}" Foreground="Red"/>
			</DataTemplate>
			<DataTemplate x:Key="CommandMessage_End_Template">
			    <TextBlock Text="{Binding Text}" Foreground="Gray"/>
			</DataTemplate>
		
		<DataTemplate x:Key="UnknownMessageTemplate">
		    <TextBlock Text="{Binding Text}" Foreground="Blue"/>
		</DataTemplate>
	</UserControl.Resources>

Da das ganze doch ganz schön viel schreib und verwaltungsarbeit ist wollte ich mal wissen ob man das ganze auch irgendwie schöner und kürzer lösen könnte?

Träume nicht dein Leben sondern lebe deinen Traum.
Viele Grüße, David Teck

6.862 Beiträge seit 2003
vor 14 Jahren

Hallo,

Hallo, der TemplateSelector ist hier nicht die richtige Wahl, da das Template ja an sich immer das gleiche ist.

Am einfachsten gehts mit nem Converter welcher dir zu nem bestimmten MessageType die Farbe zurückgibt. Den verwendest du dann beim Binding wo du die Farbe der TextBox einfach gegen dein Item bindest. Um nen switch wirst nicht rumkommen, aber du ersparst dir die ganzen Templates.

Baka wa shinanakya naoranai.

Mein XING Profil.

TripleX Themenstarter:in
328 Beiträge seit 2006
vor 14 Jahren

danke schön, dass ist doch viel besser als meine Methode 👍

Nur hab ich da ein kleines problemchen und zwar bleibt die Farbe des Textes immer schwarz. Zum testen habe ich einen einfache ValueConverter geschrieben, der mir immer die Farbe rot zurückgibt:


    [ValueConversion(typeof(Message), typeof(Brush))]
    public class MessageToBrushConverter : IValueConverter
    {
        public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return Brushes.Red;
        }
        //...

	<UserControl.Resources>
        <VC:MessageToBrushConverter x:Key="MessageToBrushConverter" />
				
		<DataTemplate x:Key="ListBoxItemTemplate">
		    <TextBlock Text="{Binding Text}" Foreground="{Binding Converter={StaticResource MessageToBrushConverter}}"/>
		</DataTemplate>
        
	</UserControl.Resources>

Also wenn ich einen Breakpoint in der Convert Funktion setze springt er rein, und gibt dann Brushes.Red zurück. Nur bleibt die Farbe, wie gesagt Schwarz, obwohl er meiner Meinung nach auf Rot switchen sollte.

€dit: Wenn ich "Red" als String zurückgebe, dann funktionierts .... wie unschön 🙁

Träume nicht dein Leben sondern lebe deinen Traum.
Viele Grüße, David Teck

O
778 Beiträge seit 2007
vor 14 Jahren

Wenn du jetzt aber tatsächlich unterschiedliche Templates benutzen willst, die sich in mehr als nur der Farbe unterscheiden: Du kannst als Key der DataTemplates auch ein Type-Objekt angeben:

<DataTemplate Key="{x:Type local:RealtimeMessage}">
...
</DataTemplate>

und dann sieht der TemplateSelector ziemlich einfach aus:


public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
        {
            if (item == null) return null;
            return resources[item.GetType()] as DataTemplate;
        }

Funktioniert prima.

Georg

TripleX Themenstarter:in
328 Beiträge seit 2006
vor 14 Jahren

Danke für den Tipp, werde ich mir merken. Problem ist halt dass ich nicht nur nach dem Typ des objektes schaue, sondern auch noch nach dem Property Typ. Aber vielleicht kann man ja da auch mit Triggern arbeiten. werde ich bei Gelegenheit mal probieren. Mir gefällt aber meine jetzige Lösung ganz gut (danke talla).

Das einzige was mir halt nicht gefällt sind dass ich im Converter Strings als Rückgabeparameter verwenden muss damit es funktioniert. Mal schauen wo da der Fehler lieg.

Träume nicht dein Leben sondern lebe deinen Traum.
Viele Grüße, David Teck

T
19 Beiträge seit 2007
vor 14 Jahren

Hi,

ich versuche gerade sowas ähnliches zu implementieren.
Hast du eine Lösung zu deinem Problem gefunden?

Ich habe ebenfalls eine abstrakte Klasse und zwei Ableitungen. Pro Ableitung habe ich ein DataTemplate definiert.

Der Converter wird nun entweder ein Objekt vom Typ Ableitung1 oder Ableitung2 zurückgeben.

Das jeweilige DataTemplate müsste doch hier eingreifen und aus diesem Objekt was "vernünftiges" für den TextBlock erzeugen, damit was dargestellt werde kann.

Wenn der Converter einfach nur einen String zurücliefert, kann ich es anzeigen.
Das ist aber nicht das was ich möchte. Es soll diese konkreten Objekte zurückliefern und das jewilige DataTemplate soll sich darum kümmern, dass ich am Ende was sinnvolles für den TextBlock in der Hand habe.

6.862 Beiträge seit 2003
vor 14 Jahren

Hallo,

wenn dein Converter unterschiedliche Objekte zurückliefert die unterschiedliche Templates benutzen sollen, dann brauchst du wieder nen TemplateSelector der je nach zurückgegebenen Typ das entsprechende Template zurückgibt.

Baka wa shinanakya naoranai.

Mein XING Profil.

T
19 Beiträge seit 2007
vor 14 Jahren

Das habe ich mir schon gedacht. Habe es auch schon implementiert.
Kriege es aber trtozdem nicht gebacken.


		public override DataTemplate SelectTemplate(object item, DependencyObject container)
		{
			FrameworkElement element = container as FrameworkElement;

			if (item is Type1)
			{
				return element.FindResource("Type1") as DataTemplate;
			}
			if (item is Type2)
			{
				return element.FindResource("Type2") as DataTemplate;
			}
			return null;
		}
	}

Wie benutze ich das ganze in meiner XAML Datei?

In meiner XAML von meinem Window habe ich schon die DataTemplates unter <Window.Resources>
Es existiert nun in diesem Window ein TextBlock, der der die Daten darstellen soll.
Kannst du mir dazu ein Beispiel zeigen wie ich TextBlock + DataTemplate + Converter zusammen benutzen kann?