Laden...

Wie erstelle ich Textblöcke analog der SelectedCells eines Datagrids?

Erstellt von CombatKarl vor 3 Jahren Letzter Beitrag vor 3 Jahren 921 Views
CombatKarl Themenstarter:in
36 Beiträge seit 2020
vor 3 Jahren
Wie erstelle ich Textblöcke analog der SelectedCells eines Datagrids?

Hallo zusammen,

ich melde mich bei Euch, um mögliche Denkanstöße und Ideen zu sammeln. Ich hänge da ziemlich auf dem Schlauch und mir fehlen die Ansätze dies umsetzen zu können.

Im Anhang habe ich die Idee mal grafisch dargestellt.

Im Grunde benutze ich ein DataGrid, gebunden an eine Collection aus einer Datenbank. Es können mehrere Zelle markiert werden, die dann auch als List übergeben werden und die Zellen per Schleife abgearbeitet werden können.

Nun möchte ich, dass durch ein Command unter dem Datagrid jeweils ein Textblock erstellt wird in der entsprechenden Breite der selektierten Spalten. (die ColumnWidth ist definiert)
Dabei sollte Rücksicht genommen werden auf bereits erstellten Textblöcke, damit diese sich NICHT überlagern. Zum Schluss soll das Ganze mit dem Schließen der App erhalten bleiben und somit bereits erstellte Textblöcke auftauchen.

Wie & Wo kann ich denn Eurer Meinung nach den Einstieg in diese Thematik finden ?
Ich habe bisher im MVVM Pattern programmiert unter .net core.

Vielen Dank schon mal an jeden für die Hilfe.

<--- Wer übt, ist feige ! --->

5.657 Beiträge seit 2006
vor 3 Jahren

Die Logik (also welche Markierung zu welcher Positionierung der Textblöcke führt) hat nichts mit WPF zu tun. Dafür kannst du die Klassen anlegen, die die Zellen und die Markierungen repräsentieren, und dann dort die Texte positionieren. Die Abfrage, ob sich Textblöcke überlagern, oder in eine neue Zeile positioniert werden müssen, ist eigentlich relativ trivial. Du mußt halt nur schauen, ob sich die Start- und Endpositionen der vorhandenen Blöcke mit dem neuen Block überschneiden.

Zur Darstellung selbst würde ich wahrscheinlich nicht auf ein DataGrid zurückgreifen, weil es für einen anderen Zweck gedacht ist. Um solche Layouts zu erstellen, würde sich wahrscheinlich ein einfaches Grid in Kombination mit einem ItemsControl besser eignen. Dann kannst du Inhalte, Größen und Positionierungen von deinen ViewModels direkt an die View binden.

Für konkretere Tips müßtest du mal beschreiben, wo das Problem genau liegt.

Weeks of programming can save you hours of planning

CombatKarl Themenstarter:in
36 Beiträge seit 2020
vor 3 Jahren

Vielen Dank schon mal für die Antwort,

das Datagrid und die Textblöcke sollten auch separat voneinander behandelt werden. Das Datagrid dient zur Übersicht verschiedener Tage und die angelegten Textblöcke repräsentieren dabei diverse Erläuterungen zu den Tagen.

Bsp. anhand des Screenshots:
Ich habe eine Schulung an den Tagen 2, 3, 4 & 5. Die genannten Zellen werden markiert, dann wird in meinen Datagrid in die jeweiligen Zellen das entsprechenden Kürzel für Schulung eingetragen (funktioniert auch dufte mit der im Hiintergrund arbeitenden Datenbank) und unterhalb soll automatisch der Textblock generiert werden (in Ausrichtung der markierten Zellen), in welchen dann Details zu der Schulung eingetragen werden können.

Ich hoffe das damit etwas deutlicher beschrieben zu haben.

Aktuell komme ich bereits an dem Punkt nicht weiter, wie der Code zu gestalten ist, wenn die Textblöcke unterhalb meines Datagrids erstellt & positioniert werden.

<--- Wer übt, ist feige ! --->

5.657 Beiträge seit 2006
vor 3 Jahren

Aktuell komme ich bereits an dem Punkt nicht weiter, wie der Code zu gestalten ist, wenn die Textblöcke unterhalb meines Datagrids erstellt & positioniert werden.

Ich wüßte auch nicht, wie man das so implementieren würde. Deshalb hatte ich Vorschläge gemacht, wie es funktionieren könnte.

Weeks of programming can save you hours of planning

CombatKarl Themenstarter:in
36 Beiträge seit 2020
vor 3 Jahren

Dafür kannst du die Klassen anlegen, die die Zellen und die Markierungen repräsentieren, und dann dort die Texte positionieren.

Wie könnte denn deiner Meinung nach eine solche Klasse gestaltet sein & welche Properties wären denn notwendig ?

<--- Wer übt, ist feige ! --->

C
55 Beiträge seit 2020
vor 3 Jahren

Hallo,

Mein Ansatz wäre das IValueConverter Interface zu implementieren.

Grüße

5.657 Beiträge seit 2006
vor 3 Jahren

Wie könnte denn deiner Meinung nach eine solche Klasse gestaltet sein & welche Properties wären denn notwendig ?

Ich meine eine Datenstruktur, die es dir ermöglicht, deine Anwendungslogik zu implementieren. Hier also ein regelmäßiges Grid aus Zeilen und Spalten, um Zellen zu selektieren und mit Inhalten zu befüllen. Das hat ersteinmal nichts mit der Benutzeroberfläche zu tun, siehe dazu [Artikel] Drei-Schichten-Architektur

Mit dieser Datenstruktur kannst du dann die Positionen, Größen und Inhalte der Zellen direkt an die View binden, z.B. an ein Grid oder (wie vonTh69 vorgeschlagen) an eine Canvas.

Der Unterschied zwischen Grid und Canvas wäre, daß ein Grid nur regelmäßige Zeilen und Spalten besitzt, und Elemente in einem Canvas völlig frei positionierbar sind.

Weeks of programming can save you hours of planning

CombatKarl Themenstarter:in
36 Beiträge seit 2020
vor 3 Jahren

Danke schon mal an alle Hilfeleistenden,

ich denke, dass ich gedanklich so langsam in die richtige Richtung komme und auch verstanden habe warum. Ich werde mal ein paar Ansätze ausprobieren und vermutlich dabei noch das eine oder andere Mal auf eure Hilfe angewiesen sein....dann wohl mit konkreterem Code.

<--- Wer übt, ist feige ! --->

CombatKarl Themenstarter:in
36 Beiträge seit 2020
vor 3 Jahren

Hallo zusammen,

ich habe nun mal die ersten Versuche mit euren Hinweisen gestartet und komme ganz gut voran. Der aktuelle Code sieht momentan so aus:


public class MainViewModel : ViewModelBase
    {
        public MainViewModel()
        {
            TextblockCommand = new RelayCommand<IList<DataGridCellInfo>>((myList) =>TextBlockMethod(myList));
            Info = new ObservableCollection<InfoModel>();
            GetPerson();
        }

        public ICollectionView ItemsPerson { get; set; }
        public ObservableCollection<InfoModel> Info { get; set; }
        public RelayCommand<IList<DataGridCellInfo>> TextblockCommand { get; private set; }
        public ObservableCollection<DataGridCellInfo> SelectedCells { get; set; }

        private void TextBlockMethod(IList<DataGridCellInfo> list)
        {
            Info.Add(new InfoModel()
            {
                Width = (LastSelectedColumn(list) - FirstSelectedColumn(list)) * 50,
                Left= FirstSelectedColumn(list) * 50,
                Top=0

            });

        }

        private int FirstSelectedColumn(IList<DataGridCellInfo> list)
        {
            if(list.Count != 0)
            {
                return list[0].Column.DisplayIndex;
            }
            else
            {
                return 0;
            }
        }

        private int LastSelectedColumn(IList<DataGridCellInfo> list)
        {
            List<int> nums = new List<int>();

            if (list.Count != 0)
            {
                foreach (DataGridCellInfo cell in list)
                {
                    nums.Add(cell.Column.DisplayIndex);
                }
                return nums.Max() + 1;
            }
            else
            {
                return 0;
            }

        }

        private void GetPerson()
        {
            ObservableCollection<PersonModel> Persons = new ObservableCollection<PersonModel>()
            {
                new PersonModel(),
                new PersonModel(),
                new PersonModel(),
                new PersonModel(),
                new PersonModel(),
                new PersonModel()
            };

            ItemsPerson = CollectionViewSource.GetDefaultView(Persons);
        }
    }

In XAML ist das alles so:


    <StackPanel>

        <DataGrid x:Name="MyDataGrid"
                  ItemsSource="{Binding ItemsPerson}"
                  AutoGenerateColumns="False"
                  CurrentCell="{Binding SelectedCell, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"      
                  SelectionMode="Extended"
                  SelectionUnit="Cell"
                  ColumnWidth="50"
                  IsSynchronizedWithCurrentItem="True"
                  CanUserAddRows="False"
                  HeadersVisibility="Column">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Vorname"
                                    Binding="{Binding Vorname}"/>
                <DataGridTextColumn Header="Nachname"
                                    Binding="{Binding Nachname}"/>
                <DataGridTextColumn Header="Tag 1"
                                    Binding="{Binding Tag_1}"/>
                <DataGridTextColumn Header="Tag 2"
                                    Binding="{Binding Tag_2}"/>
                <DataGridTextColumn Header="Tag 3"
                                    Binding="{Binding Tag_3}"/>
                <DataGridTextColumn Header="Tag 4"
                                    Binding="{Binding Tag_4}"/>
                <DataGridTextColumn Header="Tag 5"
                                    Binding="{Binding Tag_5}"/>
                <DataGridTextColumn Header="Tag 6"
                                    Binding="{Binding Tag_6}"/>
                <DataGridTextColumn Header="Tag 7"
                                    Binding="{Binding Tag_7}"/>
                <DataGridTextColumn Header="Tag 8"
                                    Binding="{Binding Tag_8}"/>
                <DataGridTextColumn Header="Tag 9"
                                    Binding="{Binding Tag_9}"/>
                <DataGridTextColumn Header="Tag 10"
                                    Binding="{Binding Tag_10}"/>
            </DataGrid.Columns>

        </DataGrid>

        <Button Content="Create Textblock"
                HorizontalAlignment="Center"
                FontSize="20"
                Focusable="False"
                CommandParameter="{Binding SelectedCells, ElementName=MyDataGrid}"
                Command="{Binding TextblockCommand}"/>

        <ItemsControl ItemsSource="{Binding Info}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <Canvas />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemContainerStyle>
                <Style TargetType="ContentPresenter">
                    <Setter Property="Canvas.Left" Value="{Binding Left}" />
                    <Setter Property="Canvas.Top" Value="{Binding Top}" />
                </Style>
            </ItemsControl.ItemContainerStyle>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <TextBlock Width="{Binding Width}"
                               Background="Red"/>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>


    </StackPanel>

Jetzt hänge ich allerdings ein einer vermutlich "einfachen" Stelle fest.

Um herauszufinden, ob in meinen ItemsControl noch ausreichend Platz ist, um einen weiteren Textblock hinzuzufügen, muss ich herausfinden, ob die Start -und Endpunkte der vorhandenen Blöcke sich mit dem neuen überschneiden. In diesem Fall würde der neue Textblock eine Zeile weiter rutschen und die Top-Property des Models bekommt den entsprechenden Wert zugewiesen.

Aber wie stelle ich denn fest, ob Start -und Endpunkte sich überschneiden ??

Vielen Dank schon mal !

<--- Wer übt, ist feige ! --->

5.657 Beiträge seit 2006
vor 3 Jahren

Du hast jetzt alles im ViewModel implementiert, und greifst trotzdem vom ViewModel auf die View zu, indem du DataGridCellInfo verwendest.

Die Idee der Schichtentrennung ist doch eben, daß das ViewModel nicht die View kennt und das Model nicht das ViewModel etc.

Wenn du deinen Code mal mit Unit-Tests testen würdest, dann würdest du auch merken, wie wichtig so etwas ist. Siehe dazu [Artikel] Unit-Tests: Einführung in das Unit-Testing mit VisualStudio

Lies dir nochmal die Antworten oben und die verlinkten Artikel durch, dann wird es vielleicht klarer, was gemeint ist.

Ich würde nach wie vor davon abraten, dafür ein DataGrid zu verwenden. Das macht die Sache nur unnötig kompliziert.

Aber wie stelle ich denn fest, ob Start -und Endpunkte sich überschneiden ??

Das sind doch am Ende nur (wenn alles korrekt implementiert ist) zwei Vergleiche.
Wenn ein Endpunkt oder ein Startpunkt eines Panels größer als der Startpunkt **und **kleiner als der Endpunkt eines anderen Panels ist, dann überschneiden sie sich.

Weeks of programming can save you hours of planning

4.931 Beiträge seit 2008
vor 3 Jahren

Außerdem fehlt im ViewModel bei den Eigenschaften-Settern der PropertyChanged-Aufruf.

CombatKarl Themenstarter:in
36 Beiträge seit 2020
vor 3 Jahren

Danke für die Antwort,

Du hast jetzt alles im ViewModel implementiert, und greifst trotzdem vom ViewModel auf die View zu, indem du DataGridCellInfo verwendest.

Die Idee der Schichtentrennung ist doch eben, daß das ViewModel nicht die View kennt und das Model nicht das ViewModel etc.

Ich würde nach wie vor davon abraten, dafür ein DataGrid zu verwenden. Das macht die Sache nur unnötig kompliziert.

allerdings verstehe ich jetzt nicht, warum ich auf das DataGrid verzichten soll, wenn ich es doch zum Anzeigen meiner Daten aus der Datenbank brauche ?? Und die Markierung und die damit erstellen Textblöcke darunter, sind ja "nur" optisches Beiwerk.

Welche andere Möglichkeit an Controls hab ich denn, wenn das Ziel ist, dass ich Zellen selektieren kann ??

Du hast jetzt alles im ViewModel implementiert, und greifst trotzdem vom ViewModel auf die View zu, indem du DataGridCellInfo verwendest.

Mir fällt da bei meinem Kenntnisstand keine andere Möglichkeit ein, als die "SelectedCells" - Eigenschaft des DataGrids als Commandparameter zu übergeben. Auf diese Weise hat doch das ViewModel dennoch keine Kenntnis von dem View. Oder verstehe ich das in diesem Fall falsch ?

Außerdem fehlt im ViewModel bei den Eigenschaften-Settern der PropertyChanged-Aufruf.

Ich nutze bei meinem ViewModel das PropertyChanged.Fody - Package. Das macht das Setzen von PropertyChanged überflüssig.

Und immer bitte her mit Kritik und Anregungen !

Danke

<--- Wer übt, ist feige ! --->

16.806 Beiträge seit 2008
vor 3 Jahren

D
Mir fällt da bei meinem Kenntnisstand keine andere Möglichkeit ein, als die "SelectedCells" - Eigenschaft des DataGrids als Commandparameter zu übergeben.

Wenn Dir nichts einfällt, dann google doch einfach mal, wie andere das machen 😃
Dann siehste schnell Snippets, dass das über Behaviors funktioniert.

Erster Google Treffer: WPF Datagrid: MVVM friendly way to bind SelectedCells to my ViewModel

CombatKarl Themenstarter:in
36 Beiträge seit 2020
vor 3 Jahren

Danke Abt,

das hatte ich auch schon gefunden und entdeckt.
Von mir dazu zwei Fragen:

1.)
Warum ist die Variante über Behaviors und Attached Properties die sauberere Lösung mit MVVM als die Geschichte mit dem Command - Parameter ??

2.)
In dem Beispiel - Code auf der verlinkten Seite wird auch im ViewModel auf DataGridCellInfo zurückgegriffen. Warum war die Implementierung davon in meinem ViewModel nicht der richtige Weg ?

Danke Euch !

Hinweis von Abt vor 3 Jahren

Bitte keine Full Quotes [Hinweis] Wie poste ich richtig?

<--- Wer übt, ist feige ! --->

16.806 Beiträge seit 2008
vor 3 Jahren

Bitte lies Dir die entsprechenden Beiträge auch durch.

Wenn Du aus dem ViewModel auf das DataGridCellInfo zugreifst, dann ist das eine Verletzung von MVVM mit den entsprechenden Nachwirkungen (zB. Testbarkeit etc).
Das Behavior ist natürlich nicht das ViewModel sondern genau der Weg dafür - daher ist hier der Zugriff auch legitim; weil das Behavior die Kenntnis haben muss.
Aber im ViewModel darfs nicht sein.

5.657 Beiträge seit 2006
vor 3 Jahren

Die vorgeschlagenen Lösungen sind im Grunde nur Workarounds für ein Problem, das es ohne DataGrid nicht gäbe.

Das Problem kommt daher, daß du in der View das DataGrid verwendest, und das dann wieder im ViewModel brauchst, um die Textblöcke zu positionieren.

Ein DataGrid braucht man, wenn man große Mengen an Daten anzeigen, sortieren, gruppieren usw. muß. Es ist nicht dafür gedacht, ein Layout zu erstellen. Besonders nicht, wenn man etwas _außerhalb _des DataGrids positionieren möchte, wie es bei dir der Fall ist.

Bei dir sind es offenbar wenige Daten, die in einem gleichmäßigen Raster dargestellt werden sollen. Und dann sollen in dem gleichen Raster zusätzliche Textblöcke eingefügt werden.

Dafür gibt es schon ein Steuerelement, nämlich das Grid. Und im ViewModel kannst du alles genauso konfigurieren, wie du es brauchst, und dann an die View binden. Siehe dazu meine vorherigen Beiträge.

Weeks of programming can save you hours of planning

CombatKarl Themenstarter:in
36 Beiträge seit 2020
vor 3 Jahren

Ich glaube eure Hinweise und die geposteten Links als Hilfe verstanden zu haben.

Ich stand da wohl irgendwie auf der Leitung.

Die vorgeschlagenen Lösungen sind im Grunde nur Workarounds für ein Problem, das es ohne DataGrid nicht gäbe.

Für mein Vorhaben benötige ich eine Control, in der ich in einem Tabellenraster selektieren kann. Mir erscheint deswegen das DataGrid als geeignet. Sicherlich kann man auch (wenn es "nur" um das Anzeigen der Daten in einem Raster geht) ein Grid verwenden, allerdings fehlt mir dann die Möglichkeit ein bzw. mehrere Objekte zu markieren.

<--- Wer übt, ist feige ! --->

CombatKarl Themenstarter:in
36 Beiträge seit 2020
vor 3 Jahren

Hallo zusammen,

im weiteren Verlauf mit meinem Vorhaben hänge ich jetzt an dem nächsten Punkt und sehe wohl den Wald vor lauter Bäumen nicht mehr.

Ich hänge einfach mit der Logik, die beim Erstellen eines neuen Textblockes prüft, ob sich der neu zu erstellende Textblock mit irgendeinem schon vorhandenen überschneidet. Ist das so, dann soll die Property "Top" vom InfoModel um einen weiteren Faktor erhöht werden und dann wird erneut geprüft, ob in dieser Zeile Überschneidungen vorhanden sind.

Ich verrenne mich da momentan von einer If - Klausel zur nächsten.

Danke schon mal für eure Hilfe !!


        public MainViewModel()
        {
            TextblockCommand = new RelayCommand(TextBlockMethod);
            SelectedGridCellCollection= new List<DataGridCellInfo>();
            Info = new ObservableCollection<InfoModel>();
            GetPerson();
        }

        public ICollectionView ItemsPerson { get; set; }
        public ObservableCollection<InfoModel> Info { get; set; }
        public RelayCommand TextblockCommand { get; private set; }
        public IList<DataGridCellInfo> SelectedGridCellCollection { get; set; }

        private void TextBlockMethod()
        {

            Info.Add(new InfoModel()
            {
                Width = (LastSelectedColumn(SelectedGridCellCollection) - FirstSelectedColumn(SelectedGridCellCollection)) * 50,
                Left = FirstSelectedColumn(SelectedGridCellCollection) * 50,
                Top = GetTop() * 20
            });

        }

        private int GetTop()
        {
            int factor = 0;
            double newStart = FirstSelectedColumn(SelectedGridCellCollection);
            double newEnd = LastSelectedColumn(SelectedGridCellCollection);

            foreach(InfoModel info in Info)
            {
                double savedStart = info.Left / 50;
                double savedEnd = savedStart + (info.Width / 50);

                if (newStart <= savedStart)
                {
                    if (!(newEnd <= savedStart))
                    {
                        factor++;
                    }
                }
                else
                {
                    if (!(newStart >= savedEnd))
                    {
                        factor++;
                    }
                }

            }

            return factor;
        }

        private int FirstSelectedColumn(IList<DataGridCellInfo> list)
        {
            if(list.Count != 0)
            {
                return list[0].Column.DisplayIndex;
            }
            else
            {
                return 0;
            }
        }

        private int LastSelectedColumn(IList<DataGridCellInfo> list)
        {
            List<int> nums = new List<int>();

            if (list.Count != 0)
            {
                foreach (DataGridCellInfo cell in list)
                {
                    nums.Add(cell.Column.DisplayIndex);
                }
                return nums.Max() + 1;
            }
            else
            {
                return 0;
            }

        }

        private void GetPerson()
        {
            ObservableCollection<PersonModel> Persons = new ObservableCollection<PersonModel>()
            {
                new PersonModel(),
                new PersonModel(),
                new PersonModel(),
                new PersonModel(),
                new PersonModel(),
                new PersonModel()
            };

            ItemsPerson = CollectionViewSource.GetDefaultView(Persons);
        }
    }

<--- Wer übt, ist feige ! --->

5.657 Beiträge seit 2006
vor 3 Jahren

Ich weiß auch nicht, wie man dir da weiterhelfen sollte. Normalerweise ist die Herangehensweise immer die gleiche:* Zuerst erstellt man ein Datenmodell, mit dem man alle Funktionen und Berechnungen durchführen kann, die für das Programm benötigt werden

  • Dann implementiert man die Funktionen mit Hilfe des Datenmodells
  • Dann testet man alle Funktionen mit Hilfe von Unit-Tests
  • Und zum Schluß erstellt man eine Benutzeroberfläche für das Programm.

Du willst die ersten Schritte auslassen, und mit dem letzten Schritt beginnen. Dann kommst du von einem Problem zum nächsten, und von einem Workaround zum anderen, und keiner kann mehr deine Herangehensweise nachvollziehen, oder deinen Code verstehen, um dir da weiterhelfen zu können. Überleg dir nur mal, an wie vielen Stellen du deinen Code anpassen müßtest, wenn sich die Spaltenbreite im DataGrid von 50 auf 100 Pixel ändern würde.

Niemand wird dich davon abhalten, so weiterzumachen. Aber dann kannst du halt auch keine Hilfe im Forum erwarten, weil niemand so arbeiten würde. Mit dem Debugger könntest du dich von Rechenschritt zu Rechenschritt hangeln, und nachvollziehen, was dein Code macht, und dann berichtigen. Siehe dazu [Artikel] Debugger: Wie verwende ich den von Visual Studio?

Empfehlen würde ich allerdings eine etwas strukturiertere Herangehensweise, und erstmal ein geeignetes Datenmodell zu erstellen. Dann kannst du deine Berechnungen so implementieren, daß sie für dich (und andere) auch in ein paar Wochen oder Monaten noch nachvollziehbar sind.

Weeks of programming can save you hours of planning