Laden...
AnnaBauer21
myCSharp.de - Member
5
Themen
20
Beiträge
Letzte Aktivität
vor einem Monat
Dabei seit
27.01.2020
Erstellt vor einem Monat

Danke für den Tipp, werde ich mir mal ansehen 😃

Erstellt vor einem Monat

Hallo,
Ich habe eine Lösung gefunden.

Ich befinde mich im Event „LoadingRow“ des DataGrids.

Das Binding wird neu gesetzt, aber da die Row gerade beim "Laden" ist, sehe ich keine Änderungen im Text.

Nachdem ich das Binding gesetzt habe, rufe ich nun eine Methode mit „BeginInvoke DispatcherPriority.Loaded“ auf, die das Highlighting durchführen soll.

In dieser Methode ist die Row dann geladen und die Textblöcke beinhalten die richtigen Texte.

Erstellt vor einem Monat

Hallo Alf,

tatsächlich klappt es, wenn ich im XAML den BindingMode TwoWay verwende.

Es scheint, als würde bei TwoWay die Bindinginformation nicht verloren gehen, bei OneWay aber schon.

Aber ich suche auch eine Lösung dafür, wenn OneWay verwendet wird, dass es durch das neue Setzen des Bindings den Text nicht verwirft sondern entsprechend dem Binding, den Text aktualisert.

Lieben Gruß

Erstellt vor einem Monat

Hallo ihr Lieben,

ich habe ein kleines Testprojekt geschrieben um mein Problem einfach zu veranschaulichen.

Folgender Ablauf:

  1. Ich habe im XAML einen TextBlock mit einem Binding zu einer RelativSource.
  2. Im CodeBehind werden dann die Inlines des TextBlocks mit Highlighting Informationen erweitet
  3. Durch DataGrid mit VirtualizingStackPanel.VirtualizationMode="Recycling" ist im Event DataGrid.OnLoadingRow aber auch im Event DataGridRow.Loaded das Ergebnis von GetBindingExpression(TextBlock.TextProperty) null
  4. Nun habe ich versucht, das Binding neu zu setzen
  5. Doch das führt dazu, dass der Text im TextBlock mit dem Default-Wert ("") ersetzt wird, statt anhand dem Binding den richtigen Text heranzuziehen.

Ich hoffe ihr könnt mir helfen, wie ich es schaffen kann, dass der Text nach dem Setzen des Bindings vorhanden ist.

XAML

<StackPanelTag="{Binding Path=MissionName, Converter={StaticResource DebugConverter}}">
    <TextBlock
        Text="{Binding Path=Tag, RelativeSource={RelativeSource AncestorType=StackPanel}" />
</StackPanel>

Code Behind

// Daten für Binding merken

var exp = txtBlock.GetBindingExpression(TextBlock.TextProperty);
txtBlock.Tag = new Tuple<RelativeSource, PropertyPath>(exp.ParentBinding.RelativeSource, exp.ParentBinding.Path)


var inlines = new List<Inline>();
inlines.Add(new Run(text.Substring(0, 1)));
inlines.Add(new Run(text.Substring(1, 2))
        {
            Background = filterData.SelectionColor,
            Foreground = filterData.ForegroundColor
        });
inlines.Add(new Run(text.Substring(3, 1)));

txtBlock.Inlines.Clear();
txtBlock.Inlines.AddRange(inlines);

Im Event

var tagData = (Tuple<RelativeSource, PropertyPath>) txtBlock.Tag;

var binding = new Binding { RelativeSource = tagData.Item1, Path = tagData.Item2};
txtBlock.SetBinding(TextBlock.TextProperty, binding);	// <- txtBlock.Text ist jetzt ""


// Dieser Aufruf sagt jetzt, IsDirty="true" & Status="Unattached"
var exp = txtBlock.GetBindingExpression(TextBlock.TextProperty);

Ich hoffe ihr habe eine Idee.

Ich weiß, es entspricht nicht MVVM, aber aktuell versuche ich das so um Laufen zu bringen, muss ja irgendwie gehen 😃

Vielen Dank euch allen

Lieben Gruß

Erstellt vor 3 Monaten

Hallo ihr Lieben,

ich habe eine Lösung gefunden. Ist etwas unschön, aber in meinem Fall die einzige die ich gefunden habe.

Die Virtualisierung nutzt beim Scrollen die alten Listelenemte und fügt sie unten wieder an.
Der DataContext hat sich entsprechend dem neuen Listelement aktualisiert, aber die Texte im TextBlock nicht, diese werden aber für das Highlighting verwendet.
Die Lösung war in meinem Fall, dass ich mit im Tag-Property des TextBlocks gewisse BindingExpressions merke und sobald beim Scrollen die nächste Row geladen ist (Event LoadingRow im DataGrid), ich die BindingExpressions entsprechend dem aktualisierten DataContext neu setze.
Das funktioniert aber nur , wenn die GUI bereits gerendert ist. Wird der Code zu früh ausgeführt, sind die BindingExpressions null.
Deshalb ist es wichtig, dass LoadingRow erst nach dem Rendern und nach der eigentlichen Highlighting-Aktion ausgeführt wird.

Wichtigste Änderungen im Überblick

Formular9.xaml

  • Verwendung von MyGrid statt DataGrid
  • Angabe der Virtualisierung
  • Neues Property 'RaiseHighlight'
  • Ganzer Code Formular9.xaml
<local:MyGrid
    ...
    local:Highlighter9.RaiseHighlight="{Binding IsFilterReady, Mode=OneWay}"
    EnableRowVirtualization="True"
    VirtualizingStackPanel.VirtualizationMode="Recycling"/>

Formular9.xaml.cs

  • Neues Property IsFilterReady, das auf true gesetzt wird, nachdem die Liste durch Refresh gefildert wurde
  • Ganzer Code Formular9.xaml.cs
public string Filter
{
    get => this.filter;
    set
    {
        this.filter = value;
        this.RaisePropertyChanged();

        this.DisplayedItems.Refresh();
        this.IsFilterReady = true;
    }
}

public bool IsFilterReady
{
    get => this.isFilterReady;
    set
    {
        this.isFilterReady = value;
        this.RaisePropertyChanged();
    }
}

MyGrid.cs

  • Eigenes DataGrid, das benötigte Funktionalitäten bereitstellt
public class MyGrid : DataGrid
{
    public MyGrid()
    {
        this.LoadingRow += GridOnLoadingRow;
    }

    public bool IsHighlightingReady { get; set; }

    public bool IsVirtualization => VirtualizingPanel.GetIsVirtualizing(this) || this.EnableColumnVirtualization || this.EnableRowVirtualization;

    public VirtualizationMode VirtualizationMode => VirtualizingPanel.GetVirtualizationMode(this);

    public string Filter { get; set; }

    private void GridOnLoadingRow(object sender, DataGridRowEventArgs e)
    {
        Highlighter9.GridOnLoadingRow((MyGrid)sender, e);
    }
}

Highlighter9.cs

  • Neues Property 'RaiseHighlight' zum Einstieg der Verarbeitung
  • LoadingRow Event, das nur ausgeführt wird, wenn das eigentliche Highlighten durchgeführt wurde
  • Ermitteln & setzen der BindingExpressions
  • Ganzer Code Highlighter9.cs
public static class Highlighter9
{
    // Feuert den Start des Highlighting Prozesses
    public static readonly DependencyProperty RaiseHighlightProperty = DependencyProperty.RegisterAttached(
        ...
        new PropertyMetadata(false, null, CoerceValueCallback));

    ...

    private static object CoerceValueCallback(DependencyObject d, object baseValue)
    {
        // Es ist wichtig zu warten, bis die Liste gefildert wurde, bevor das Highlighting durchgeführt wird.
        // Wenn die Listenelemente nicht vollständig gerendert sind fehlen Informationen, die nötig sind wie z.B. BindingExpression.
        if ((bool)baseValue)
        {
            ...

            var grid = (MyGrid)d;
            grid.IsHighlightingReady = false;

            Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(() =>
            {
                DoAction(grid, filter);
            }));
        }

        return baseValue;
    }

    ...

    private static void DoAction(MyGrid grid, string filter)
    {
        // Aktuelle Daten im Grid speichern, damit Grid nach verzögertem Aufruf die richtigen Daten verwendet.
        // Bei mehreren Grids im Formular kommt es sonst zu Komplikationen, dass die Grids die Daten des anderen anziehen, da der Behavior statisch ist.
        // Zudem benötigt Grid die Daten damit im Event LoadingRow die richtigen Daten bekannt sind
        grid.Filter = filter;

        var gridRows = grid.GetDescendants<DataGridRow>().ToList();
        foreach (var row in gridRows)
        {
            HighlightRow(row, grid);
        }

        grid.IsHighlightingReady = true;
    }

    ...

    private static void HighlightTextBlock(TextBlock txtBlock, MyGrid grid)
    {
        // Prüfen, ob Virtualisierung aktiviert ist
        if (grid.IsVirtualization && grid.VirtualizationMode == VirtualizationMode.Recycling)
        {
            // Property Name aus Binding ermitteln & merken
            var exp = txtBlock.GetBindingExpression(TextBlock.TextProperty);
            txtBlock.Tag = exp?.ResolvedSourcePropertyName;
        }

        ...
    }
    
    public static void GridOnLoadingRow(MyGrid grid, DataGridRowEventArgs e)
    {
        // Bei erneuter Filtereingabe wird das Event LoadingRow durchgeführt, bevor die eigentliche Verarbeitung des Highlighters durchgeführt wird.
        // Die Ausführung des Codes so lange ignorieren, bis der Highlighter seine Verarbeitung abgeschlossen hat.
        // Führt sonst zum Fehler, dass BindingExpression Informationen nicht vorhanden sind
        if (!grid.IsHighlightingReady)
        {
            return;
        }

        // Bei Deaktivierter Virtualisierung Methode verlassen
        if (!grid.IsVirtualization)
        {
            return;
        }

        if (grid.VirtualizationMode == VirtualizationMode.Recycling)
        {
            // Textblöcke der Row ermitteln
            if (e.Row.GetDescendants<TextBlock>().Any())
            {
                HighlightRowWithUpdateBinding(e.Row, grid);
            }
            else
            {
                // Werden keine Textblöcke gefunden, ist Row zwar geladen, aber noch nicht gerendert. Aufruf verzögern.
                Application.Current.Dispatcher.BeginInvoke(new Action(() =>
                {
                    if (e.Row.GetDescendants<TextBlock>().Any())
                    {
                        HighlightRowWithUpdateBinding(e.Row, grid);
                    }
                }), DispatcherPriority.Send);
            }
        }
        else
        {
            // Hier ist die Row noch nicht gerendert, GetDescendants in HighlightRow findet keine Textboxen, Aufruf verzögern
            Application.Current.Dispatcher.BeginInvoke(new Action(() =>
            {
                HighlightRow(e.Row, grid);
            }), DispatcherPriority.Send);
        }
    }

    private static void HighlightRowWithUpdateBinding(DataGridRow row, MyGrid grid)
    {
        // Bindings für die TextBlöcke neu setzen
        foreach (var txtBlock in row.GetDescendants<TextBlock>())
        {
            // Property Name aus Binding oder Tag-Property ermitteln
            var exp = txtBlock.GetBindingExpression(TextBlock.TextProperty);
            var path = exp != null ? exp.ResolvedSourcePropertyName : txtBlock.Tag.ToString();

            // Binding neu setzen
            var binding = new Binding { Source = txtBlock.DataContext, Path = new PropertyPath(path) };
            txtBlock.SetBinding(TextBlock.TextProperty, binding);
        }

        HighlightRow(row, grid);
    }

MyListItem.cs

  • Unverändert, siehe oben
Erstellt vor 4 Monaten

Ich habe gerade gesehen, dass in der Methode HighlightTextBlock(TextBlock) direkt beim Einstieg txtBlock.GetBindingExpression(TextBlock.TextProperty) die entsprechenden Daten liefert, aber am Ende der Methode, nachdem die Inlines gesetzt wurden, liefert txtBlock.GetBindingExpression(TextBlock.TextProperty) null.

Da scheint wohl doch das Binding kaputt zu gehen.

Erstellt vor 4 Monaten

Guten Morgen,

genau das ist leider das Problem, HighlightRow(e.Row) verwendet die Texte der Textblöcke um die Inlines zu aktualisieren, aber die Texte sind in GridOnLoadingRow noch nicht aktualisiert, im DataContext sind zwar die neuen Texte, aber in den Textblöcken im Text noch nicht.

Und ich habe im Grunde versucht irgendetwas zu machen, damit die Aktualisierung der TextBlock-Texte anhand dem DataContext neu angestoßen wird.

Erstellt vor 4 Monaten

Vielen Dank für deine schnelle Antwort.

Die Testklasse beinhaltet nur einen gewissen Ausschnitt aus dem Hauptcode um den Fehler zu zeigen, die eigentliche Highlighter Klasse kann mit den unterschiedlichsten Controls (z.B. DataGrid, TreeView, Alleinstehender TextBlock, ... ) umgehen. Egal welches Control, es werden die TextBlöcke an unterster Ebene ermittelt und der Text entsprechend markiert.

Ich glaube mein Verständnis ist für diese Thematik zu gering, ich hab mir die Links angeschaut aber verstehe nicht, wie ich das für mich nutzen kann.

Wie kann ich sowas so generisch bauen, dass es unabhängig vom ViewModel funktioniert? Ich bring in meinen Gedanken nichts zusammen. Ich müsste dort dann für jeden sichtbaren TextBlock Inlines generieren und dafür sorgen, dass das beim Scrollen auch entsprechend synchronisiert wird.

Zudem muss ich das DataGrid nutzen, da wir abgeleitete Klassen davon verwenden und die Virtualisierung durch EnableRowVirtualization zwingend erforderlich ist, da die Elemente beim Scrollen entsprechend abgebaut werden.

Hast du evt. noch eine Idee, die mit DataGrid funktionieren könnte? Evtl. eine eigene Klasse davon ableiten und darin irgendwas magisches machen, wodurch das funktionieren könnte?

Erstellt vor 4 Monaten

Danke für die Erklärung, dann habe ich mir das wohl falsch abgeschaut.

Also mit EnableRowVirtualization="false" gehts, aber mit true nicht.

Das ist nur eine Testklasse, normalerweise ist mein Code entsprechend ausgelagert.

Die Highlighter Funktionalität soll unabhängig von jeglichen ViewModels verwendet werden können, egal ob DataGrid oder TreeViewCtrl (Eigenes TreeViewControl), egal welche Daten reingeschoben werden. Der Highlighter soll stumpf erkennen, dass dort irgendwo Texte stehen und diese entsprechend markieren.

Oder habe ich das falsch verstanden? Ich habe das jetzt so verstanden, dass ich die ganze Highlighting Geschichte für jedes ViewModel separat machen muss, dass würde aber leider nicht meiner Anforderung von "Überall verwendbar" entsprechen.

Erstellt vor 4 Monaten

Hallo Th69,

danke für den Tipp!

Folgendes geht:

  1. VirtualizingPanel.IsVirtualizing="False"
  2. VirtualizingPanel.IsVirtualizing="true"+ VirtualizingStackPanel.VirtualizationMode="Standard"

Folgendes geht nicht:

  1. VirtualizingPanel.IsVirtualizing="true"+ VirtualizingStackPanel.VirtualizationMode="Recycling"

Leider ist die Virtualisierung zwingend nötig denn die Liste kann im Grunde n Einträge besitzen, aktuell ist die Einschätzung bis zu 5000.