Laden...

ComboBox-Contet Textblock formatieren (Schriftfarbe setzen) plus Binding

Erstellt von GeneVorph vor 4 Jahren Letzter Beitrag vor 4 Jahren 5.916 Views
G
GeneVorph Themenstarter:in
180 Beiträge seit 2015
vor 4 Jahren
ComboBox-Contet Textblock formatieren (Schriftfarbe setzen) plus Binding

Hallo,

bei meinem Lernprojekt habe ich eine einfache Sache vor, stelle aber gerade fest, dass es scheinbar doch nicht so leicht ist.

Ich habe ein userControl, in dem sich ein TextBlock befindet. Dieser soll später im Projekt den enthaltenen Text je nach Zustand eines Propertys in verschiedenen Farben anzeigen.
Ich habe:


<UserControl>
 <StackPanel Orientation="Horizontal">
        <ComboBox x:Name="combo" Margin="10" MinWidth="60" VerticalAlignment="Center" ItemsSource="{Binding Source={StaticResource ItemListSortedView}}" SelectedIndex="{Binding TheIndex, Mode=TwoWay}" SelectedItem="{Binding TheValue, Mode=TwoWay}">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="SelectionChanged">
                    <i:InvokeCommandAction Command="{Binding SelectionChangedCommand}"/>
                </i:EventTrigger>
                <i:EventTrigger EventName="DropDownOpened">
                    <i:InvokeCommandAction Command="{Binding SetOldValueCommand}" CommandParameter="{Binding ElementName=combo, Path=SelectedItem}"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
         
<!-- Hier der relevante Code-Teil -->
   <ComboBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding}" Foreground="{Binding Path=TheColor}"/>
                </DataTemplate>
            </ComboBox.ItemTemplate>
<!-- bis hier -->
        </ComboBox>
        <TextBox Margin="10" MinWidth="120" Text="{Binding TheText, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
    </StackPanel>
</UserControl>

Das Property "TheColor" ist im ViewModel meines UserControls hinterlegt und hat folgenden Code:

 public Color TheColor
        {
            get { return _color; }
            set
            {
                _color = Color.Red;
            }
        }

Bislang wird der Code jedoch gar nicht aufgerufen, was mich gleich zu den wichtigsten Fragen bringt.

  1. Zuerst hatte ich für den TextBlock
<TextBlock Text="{Binding SelectedItem}" ...</TextBlock>

was aber nur zur folge hatte, dass das selektierte Item so oft angezeigt wurde, wie Elemente in der Collection waren. Mehr oder minder durch Zufall habe ich herausgefunden, das einfach {Binding} zum gewünschten Ergebnis führt. Ich verstehe das nicht - gibt es dazu eine einfache, anschauliche Erklärung?

  1. Zu Testzwecken möchte ich die Textfarbe über ein Property (TheColor) setzen; später brauche ich jedoch ein DataTemplate (?), bzw. DataTriggers, weil sich die Farbgebung für den Text nach einem enum im Property richten soll (das Property TheColor wäre dann nicht vom Typ "Color", sondern ein enum). Wie müsste ich den Code dafür anlegen?

Gruß
Vorph

5.657 Beiträge seit 2006
vor 4 Jahren

weil sich die Farbgebung für den Text nach einem enum im Property richten soll

Verwende dafür einfach einen Trigger in der View.

Was willst du mit diesem Code erreichen:

 public Color TheColor
        {
            get { return _color; }
            set
            {
                _color = Color.Red;
            }
        }

Soll es evtl. so aussehen:

 public Color TheColor { get { return Color.Red; } }

Und wozu brauchst du das UserControl eigentlich? Das braucht man wirklich nur, um Steuerelemente zu erstellen, die man in anderen Projekten wiederverwenden möchte, unabhängig von irgendwelchen ViewModels. Ansonsten reicht ein Template völlig aus.

Weeks of programming can save you hours of planning

G
GeneVorph Themenstarter:in
180 Beiträge seit 2015
vor 4 Jahren

Verwende dafür einfach einen Trigger in der View.

Also einen DataTrigger? Und den binde ich dann einfach zu meinem Property? (In meinem Fall würde das Property so aussehen:


private PathItemState _itemState;

public PathItemState ItemState
{
get {return _itemState;}
set
{
 _itemState = value;
OnPropertyChanged(ref _itemState, value);
}

)

PathItemState ist ein enum; sieht folgendermaßen aus:


public enum PathItemState
{
chapterEmpty = 0,
chapterCompleted = 1,

}

Also muss mein Setter im DataTrigger auf dieses Property zeigen, und für Value wähle ich dann den jeweiligen "Zustand" aus, bei dem der Trigger anspringen und die Farbe in der TextBox des UserControls verändern soll.

Was willst du mit diesem Code erreichen:
...
Soll es evtl. so aussehen:

 public Color TheColor { get { return Color.Red; } }  

🤔 ja, so hätte es aussehen sollen. Offenbar ist der Kaffee nicht mehr das, was er mal war....

Und wozu brauchst du das UserControl eigentlich? Das braucht man wirklich nur, um Steuerelemente zu erstellen, die man in anderen Projekten wiederverwenden möchte, unabhängig von irgendwelchen ViewModels. Ansonsten reicht ein Template völlig aus.

Ich bin mir nicht 100% sicher, ob ich es mit einem Template auch so hinbekomme: ich möchte Kapitel in einem Buch kommentieren können; die Kommentare werden auf dem MainWindow dynamisch erstellt (Mist - jetzt wo ich das tippe, denke ich, "das ist womöglich genau DAS, wofür man sonst Templates verwendet..."). "Brauchen" ist vielleicht zuviel gesagt: ich bin lediglich Hobby-Coder und hatte neulich einen Blog über UserControls durchwühlt - hier hat es sich für mich einfach angeboten ein bisschen zu üben und zu experimentieren...

Noch einen Hinweis über die SAche mit dem Text="{Binding}" im Gegensatz zu Text="{Binding SelectedItem}"? Ich kann immernoch nicht ganz verorten, was ein {Binding} ohne Target-Property eigentlich bedeutet - woher weß Text (bzw. Binding) an welches Property er sich in diesem Fall binden soll?

Gruß
Vorph

5.657 Beiträge seit 2006
vor 4 Jahren

{Binding} ohne Target-Property

Dann wird direkt auf den DataContext gebunden.

Also einen DataTrigger? Und den binde ich dann einfach zu meinem Property?

So in etwa:

<DataTrigger Binding="{Binding Path=State}" Value="{x:Static my:PathItemState.chapterEmpty }">

Zu den UserControls: In Windows Forms hat es immer Sinn ergeben, bestimmte Teile der Anwendung in eigene Controls zu kapseln. Aber in WPF gibt es Templates, die viel flexibler sind, und in den allermeisten Fällen ausreichen.

Weeks of programming can save you hours of planning

G
GeneVorph Themenstarter:in
180 Beiträge seit 2015
vor 4 Jahren

Ah, da schau' an - kaum macht man's richtig, schon funktioniert's!


<ComboBox>
 <ComboBox.Style>
                <Style TargetType="ComboBox">
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding CurrentChapterItem.ChapterStatus}" Value="registeredChapter">
                            <Setter Property="Foreground" Value="Red"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </ComboBox.Style>
        </ComboBox>

Sehr schön 👅

Was mich an XAML generell frustet ist, dass - kaum glaubt man, die "Regeln" verstanden zu haben - sich gerade gelerntes selten direkt auf "next-level"-Niveau anwenden lässt. Und die Sache hier ist ein schönes Beispiel.

Ich habe in der ComboBox jetzt einen TextBlock und ein Rectangle. Sieht dann so aus:


<ComboBox x:Name="combo" Margin="10" MinWidth="80" VerticalAlignment="Center"                   
                    ItemsSource="{Binding Source={StaticResource ItemListSortedView}}" 
                    SelectedItem="{Binding CurrentChapterItem}">     
            ...

            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <Rectangle x:Name="rect" Width="10" Height="10" Margin="0,0,10,0"/>
                        <TextBlock Text="{Binding ChapterID}"/>
                    </StackPanel>
                </DataTemplate>            
            </ComboBox.ItemTemplate>
            <ComboBox.Style>
                <Style TargetType="ComboBox">
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding CurrentChapterItem.ChapterStatus}" Value="registeredChapter">
<!-- spätestens hier beschwert sich der Compiler --> rect ist als Name nicht bekannt -->
                            <Setter TargetName="rect" Property="Foreground" Value="Red"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </ComboBox.Style>
        </ComboBox>

Meine Idee war, statt dem Text das Rectangle farblich hervorzuheben. Leider funktioniert es so scheinbar nicht. Ich habe es außerdem mit einem Style in den UserControl.Ressources versucht - das hat aber genauso wenig funktioniert. Ich verstehe nicht, warum's mit Text funktioniert, mit dem Rectangle aber nicht X(

Pfft - halb drei; viel zu spät, besser ab ins Bett, das wird heut nix mehr 🙄

G
GeneVorph Themenstarter:in
180 Beiträge seit 2015
vor 4 Jahren

Womöglich habe ich die Antworten auf meinen letzten Post schon gefunden - wiedermal ist es aber so, das die für mich wesentlichen Fragen offen bleiben:

Einmal von Microsoft selbst (https://docs.microsoft.com/de-de/dotnet/api/system.windows.controls.itemscontrol.itemtemplate?view=netframework-4.8 ): Dort heißt es unter Hinweise:

Beispielsweise sind die generierten Container für ListBoxListBoxItem-Steuerelementen. bei ComboBoxhandelt es sich um ComboBoxItem-Steuerelemente. Verwenden Sie die ItemsPanel-Eigenschaft, um das Layout der Elemente zu beeinflussen.

Also, wenn ich das richtig interpretiere, kann ich das Rectangle irgendwie unter der ItemsPanel-Eigenschaft der ComboBox referenzieren oder einbinden und dann einen Style zuweisen. (Wobei ich das jetzt eher so lese, dass es hier um das generelle Erscheinungsbild geht, nicht um ein spezifisches, je nach UseCase?)

Und auch dieser Blog () lässt mich eher erahnen wie eine Lösung aussehen könnte, denn konkret behilflich zu sein. Interessanter Weise wird dasselbe Beispiel aufgenommen, wie von Microsoft (ohne Verweis auf die Quelle - na, wenn das mal kein Ärger gibt 😉 ): https://www.lernmoment.de/csharp-programmieren/datatemplate-stelle-mhelos-details-deiner-objekte-in-wpf-dar/

Insgesamt eigentlich verständlich erklärt (und hier wird auch klar, dass ich das UserControl wirklich nicht brauche), aber an den entscheidenden Stellen hätte ich mich über ein "wie" gefreut. So heißt es etwa:

Neben der ItemTemplate Eigenschaft haben Controls für Auflistungen die Eigenschaft ItemTemplateSelector. Dieser kannst du einen eigenen DataTemplateSelector zuweisen. Nun kannst du nicht nur ein DataTemplate verwenden, sondern gleich mehrere. Dabei entscheidet der DataTemplateSelector basierend auf Werten in den anzuzeigenden Objekten, welches DataTemplate benutzt werden soll.

Damit sollen sich nun bestimmte Werte hervorheben lassen. Es folgt auch ein Code-Beispiel, leider ohne Angabe wo es zu verwenden ist (Code behind? ViewModel? Anderweitig?).

Vielleicht verrenne ich mich ja aber auch gerade - falls dem so ist, wäre ich um jeden Hinweis in die richtige Richtung dennoch dankbar. Vielleicht is mein vorletzter post ja gar nicht so falsch und benötigt lediglich kleinere Korrekturen?

Vielen Dank, schönen Sonntag
Vorph

P
441 Beiträge seit 2014
vor 4 Jahren

Bezüglich des Codebeispiels - meinst du den DataTemplateSelector?

Das ist eine eigene Klasse, die dann in den Ressourcen deiner View referenziert (und ggf. instanziiert) werden muss.

5.657 Beiträge seit 2006
vor 4 Jahren

Was genau ist deine Frage?

Weeks of programming can save you hours of planning

G
GeneVorph Themenstarter:in
180 Beiträge seit 2015
vor 4 Jahren

Was genau ist deine Frage?

Sorry, das ging im Gedränge wohl unter:
Ich hatte es mit deiner Hilfe geschafft den Text im TextBlock der ComboBox je nach "Zustand" eines enums farblich zu verändern. Das Gleiche wollte ich jetzt auf ein Rectangle anwenden, bin aber gestern abend/heute früh stundenlang gescheitert.

Obwohl ich mir sicher war, dass ich es an diesem Punkt verstanden hatte - ich konnte mir leider nicht erklären, wo mein Fehler lag. Fälschlicherweise habe ich mir zu früh Asche auf's Haupt gestreut, weil mein Fazit das war, dass ich es total falsch versuche.

Mittlerweile habe ich die Lösung gefunden --> mein Code musste nur um folgende Zeile ergänzt werden:

<DataTrigger Binding = "{Binding ElementName=combo, path=SelectedItem.ChapterStatus}" Value="registeredChapter" />

Hingegen hatte ich zuvor:

<DataTrigger Binding="{Binding CurrentChapterItem.ChapterStatus}" Value="registeredChapter">

CurrentChapterItem ist ein Property des ViewModels. Meine Combobox binded bereits per SelectedItem an dieses Property, so dass ich der irrigen (?) Annahme war, ich könnte mit dem Rectangle genauso verfahren. Klappt dann aber nicht. Stattdessen musste ich ausdrücklich die Combobox referenzieren (Binding ElementName=comb0).

Gut, ich hab's ja jetzt hinbekommen. Dennoch, konkrete Frage: worin liegt der Unterschied zwischen
"Binding CurrentChapterItem.ChapterStatus" und "Binding ElementName=combo, Path=SelectedItem"?

Ich meine, ich verstehe schon, dass sich ElementName auf die ComboBox direkt bezieht, was ich ich nicht verstehe ist die Tatsache, dass das SelectedItem-Property der ComboBox auf das CurrentChapterItem des ViewModels bindet. Wenn ich aber das Rectangle direkt an genau dasselbe Property binde, reagiert es nicht. Hat das damit zu tun, dass sich das Rectangle im DataTemplate der ComboBox befindet?.
Gruß
Vorph

5.657 Beiträge seit 2006
vor 4 Jahren

Das Binding mit ElementName bezieht sich auf ein Steuerelement in der View, an dessen Eigenschaften du binden kannst. Ansonsten bezieht sich das Binding auf den DataContext des Elements. In deinem Fall wäre ein einfacher Trigger (kein DataTrigger) die beste Lösung. Wobei mir trotz deines umfangreichen Textes nicht klar ist, was du eigentlich genau erreichen willst.

Wenn ein Binding nicht korrekt funktioniert, kannst du eine Fehlermeldung im Ausgabefenster finden, oder es selbst überprüfen, siehe Abschnitt Debugging in [Artikel] MVVM und DataBinding.

Weeks of programming can save you hours of planning

G
GeneVorph Themenstarter:in
180 Beiträge seit 2015
vor 4 Jahren

Danke für den Link, Mr.Sparkle - das Kapitel mit über's Debugging hat mir schon mal gleich geholfen =)

Wobei mir trotz deines umfangreichen Textes nicht klar ist, was du eigentlich genau erreichen willst.

War zu viel Info, wa? 😁
Mein UserControl verfügt über ein Rectangle und eine TextBox. Das Rectangle besitzt ein Binding auf ein Property des ViewModels, das meine View darüber unterrichtet, ob ein Kapite in einem Buchl schon kommentiert wurde (registeredChapter) oder nicht (unregisteredChapter). Einmal Farbton grün - einmal rot.

Jetzt, wo ich weiß wie es geht - eigentlich total simpel. Meine Lösung s. oben.

as Binding mit ElementName bezieht sich auf ein Steuerelement in der View, an dessen Eigenschaften du binden kannst. Ansonsten bezieht sich das Binding auf den DataContext des Elements.

Den Teil hatte ich schon verstanden. Was ich nicht verstehe: der DataContext des Elements 'combo' ist das PathControlViewModel. Daran ist es gebunden.
In meinem ersten Versuch das Rectangle einzufärben hatte ich einfach

<DataTrigger Binding="{Binding CurrentChapterItem.ChapterStatus}" Value="registeredChapter">

im Code. Ohne ElementName. Dabei ist CurrentChapterItem ja genau jenes Property, das sich im DataContext von 'combo' befindet.
Meine Frage war also mehr oder minder: Müssten nicht beide Bindings (s. meinen vorherigen Post) zum selben Ergebnis führen?
Ich weiß, ich hab' da einen Denkfehler, komme aber nicht drauf...

5.657 Beiträge seit 2006
vor 4 Jahren

War zu viel Info, wa? 😄

Zu viel Text, zu wenig Information. Du mußt nicht jeden Gedankengang hier dokumentieren. Poste den relevanten Code und eine konkrete Frage, dann machst du es deinen Helfern leichter.
Siehe [Hinweis] Wie poste ich richtig?, Punkt 5

Wenn du eine neue Frage hast, erstelle ein neues Thema und poste den relevanten Code und eine konkrete Frage. So verliert man nicht den Überblick.
Siehe [Hinweis] Wie poste ich richtig?, Punkt 1.2

Dabei ist CurrentChapterItem ja genau jenes Property, das sich im DataContext von 'combo' befindet.

Das läßt sich von hier aus nicht sagen, weil nicht klar ist, wie dein Code jetzt aussieht. Aber du könntest es z.B. mit einem DebugConverter mit wenig Aufwand selbst überprüfen.

Weeks of programming can save you hours of planning