DataDrid einzelne Zelle selektieren per Code

  • WPF

Es gibt 6 Antworten in diesem Thema. Der letzte Beitrag () ist von eichseinet.

    DataDrid einzelne Zelle selektieren per Code

    Hallo zusammen,

    seit ein paar Stunden versuche ich nun eine einzelne Zelle in einem Grid per Code zu selektieren. Die Zelle soll so angewählt / markiert sein, als hätte man sie mit einem einfachen Mausklick angewählt. Die Selectionunit ist auf "Cell or Row" eingestellt. Per Mausklick wählt man also nur eine Zelle und nicht die ganze Zeile aus.
    folgendes hab ich u.a. probiert:

    VB.NET-Quellcode

    1. Zelle = New System.Windows.Controls.DataGridCellInfo(Beispiel_Coll01(0), Beispiel_DataGrid01.Columns(2))
    2. Beispiel_DataGrid01.CurrentCell = Zelle
    3. Beispiel_DataGrid01.Focus()


    folgender Code markiert zwar die Zelle, aber sie kann weder bearbeitet noch per Tab oder Pfeiltaste verschoben werden.

    VB.NET-Quellcode

    1. Beispiel_DataGrid01.CurrentCell = New DataGridCellInfo(Beispiel_Coll01(0), Beispiel_DataGrid01.Columns(2))
    2. Beispiel_DataGrid01.SelectedCells.Clear()
    3. Beispiel_DataGrid01.SelectedCells.Add(Beispiel_DataGrid01.CurrentCell)
    4. Beispiel_DataGrid01.Focus()


    Außer völlige Murks kommt da aber nix bei rum.
    Wie aktiviert man denn nun eine Zelle über den Code?

    Gruß

    eddi

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „eichseinet“ ()

    Hallo @eichseinet

    Das ist ist nicht ganz so einfach und recht Tricky.
    Das problem welches ich auch immer wieder prädige ist eben das die WPF auf Binding ausgelegt ist.

    Gerade das DataGrid unter WPF ist schon eine spezielle Sache. Vorallem weil als Container ein VirtualizingStackPanel dient. Die Elemente werden Virtualisiert.
    Kannste mal Googeln. Im Grunde dient es der Performance. Wenn du 1 Mio. Datensätze in ein DataGrid mit 12 Stalten lädst hätte das Ding schon zu tun. Deshalb wird Virtualisiert. Das bedeutet das nicht alle Elemente richtig geladen werden sondern nur die, die im View sichtbar sind. Scrollst du nun durch die Zeilen werden die Elemente "nachgeladen" und die Elemente die nun nicht mehr sichtbar sind werden "Recycled".

    Das macht es schwierig eine Funktion zu schreiben welche "sicher" eine Celle markieren kann. Warum? Wer sagt denn das die Celle da ist? Wenn diese nicht geladen ist weil die Zeile nicht sichtbar ist dann ist sie eben nicht da. Also muss sichergestellt werden das diese mal geladen wird usw.

    Du merkst schon, nicht so Easy.

    Hier ein kleiner test von mir:

    XML-Quellcode

    1. <Window x:Class="MainWindow"
    2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    4. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    6. xmlns:local="clr-namespace:DataGridTest"
    7. mc:Ignorable="d"
    8. Title="MainWindow" Height="450" Width="800">
    9. <Grid>
    10. <DataGrid x:Name="MyDataGrid" SelectionUnit="Cell"/>
    11. </Grid>
    12. </Window>


    VB.NET-Quellcode

    1. Imports System.Windows.Controls.Primitives
    2. Public Class MainWindow
    3. Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
    4. Dim cars As New List(Of Car) From {
    5. New Car() With {.Marke = "VW", .Modell = "Golf", .PS = 130},
    6. New Car() With {.Marke = "VW", .Modell = "Caddy", .PS = 90},
    7. New Car() With {.Marke = "Seat", .Modell = "Ibiza", .PS = 110},
    8. New Car() With {.Marke = "Audi", .Modell = "A4 Avant", .PS = 180}}
    9. Me.MyDataGrid.ItemsSource = cars
    10. SelectCellByIndex(MyDataGrid, 1, 1) 'In der zweiten Zeile die zweite Spalte markieren und Focusieren
    11. End Sub
    12. Public Sub SelectCellByIndex(ByVal dataGrid As DataGrid, ByVal rowIndex As Integer, ByVal columnIndex As Integer)
    13. If Not dataGrid.SelectionUnit.Equals(DataGridSelectionUnit.Cell) Then
    14. Throw New ArgumentException("The SelectionUnit of the DataGrid must be set to Cell.")
    15. End If
    16. If rowIndex < 0 OrElse rowIndex > (dataGrid.Items.Count - 1) Then
    17. Throw New ArgumentException(String.Format("{0} is an invalid row index.", rowIndex))
    18. End If
    19. If columnIndex < 0 OrElse columnIndex > (dataGrid.Columns.Count - 1) Then
    20. Throw New ArgumentException(String.Format("{0} is an invalid column index.", columnIndex))
    21. End If
    22. dataGrid.SelectedCells.Clear()
    23. Dim item As Object = dataGrid.Items(rowIndex)
    24. Dim row As DataGridRow = CType(dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndex), DataGridRow)
    25. If row Is Nothing Then
    26. dataGrid.UpdateLayout()
    27. dataGrid.ScrollIntoView(item)
    28. row = CType(dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndex), DataGridRow)
    29. End If
    30. If Not row Is Nothing Then
    31. Dim cell As DataGridCell = GetCell(dataGrid, row, columnIndex)
    32. If Not cell Is Nothing Then
    33. Dim dataGridCellInfo As DataGridCellInfo = New DataGridCellInfo(cell)
    34. dataGrid.SelectedCells.Add(dataGridCellInfo)
    35. cell.Focus()
    36. End If
    37. End If
    38. End Sub
    39. Public Function GetCell(ByVal dataGrid As DataGrid, ByVal rowContainer As DataGridRow, ByVal column As Integer) As DataGridCell
    40. If Not rowContainer Is Nothing Then
    41. Dim presenter As DataGridCellsPresenter = CType(FindVisualChild(Of DataGridCellsPresenter)(rowContainer), DataGridCellsPresenter)
    42. If presenter Is Nothing Then
    43. rowContainer.ApplyTemplate()
    44. presenter = CType(FindVisualChild(Of DataGridCellsPresenter)(rowContainer), DataGridCellsPresenter)
    45. End If
    46. If Not presenter Is Nothing Then
    47. Dim cell As DataGridCell = CType(presenter.ItemContainerGenerator.ContainerFromIndex(column), DataGridCell)
    48. If cell Is Nothing Then
    49. dataGrid.ScrollIntoView(rowContainer, dataGrid.Columns(column))
    50. cell = CType(presenter.ItemContainerGenerator.ContainerFromIndex(column), DataGridCell)
    51. End If
    52. Return cell
    53. End If
    54. End If
    55. Return Nothing
    56. End Function
    57. Public Function FindVisualChild(Of T)(ByVal obj As DependencyObject) As Object
    58. Dim i As Integer = 0
    59. Do While i < VisualTreeHelper.GetChildrenCount(obj))
    60. Dim child As DependencyObject = VisualTreeHelper.GetChild(obj, i)
    61. If child IsNot Nothing AndAlso TypeOf child Is T Then
    62. Return child
    63. Else
    64. Dim childOfChild As T = CType(FindVisualChild(Of T)(child), T)
    65. If childOfChild IsNot Nothing Then
    66. Return childOfChild
    67. End If
    68. End If
    69. i = (i + 1)
    70. Loop
    71. Return Nothing
    72. End Function
    73. End Class


    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Oh mein Gott!
    Vielen Dank erst mal (für den Code UND die Erklärung). Hab den Code gerade mal getestet und es funktioniert. Muss das jetzt noch in mein Testprojekt einbinden.
    Und dieser Aufwand für ein bischen Bedienkomfort... Nach dem Neuaufbau meiner Collection soll der Cursor einfach an der alten Stelle stehen.

    Gruß
    eddi
    Aber dafür müsstest du ja nicht die Zelle markieren. Dafür würde doch die Zeile genpgen oder? MyDataGrid.SelectedItem oder MyDataGrid.SelectedIndex würde hier reichen. Dann spart man sich das alles.

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Mit den beiden Methoden hatte ich auch schon probiert, aber es wird einfach nix markiert. Auch dann nicht, wenn die Selectionunit auf "FullRow" eingestellt ist. Einzeln markierte Zellen find ich einfach viel übersichlicher, als nur die Umrahmung der aktiven Zellen in einer Zeile.
    Die Collection hinter dem Grid wird aus einer "BasisCollection" nach jeder Änderung komplett gelöscht und neu aufgebaut. Es handelt sich um Timer. Mehrere Einzelzeiten werden u.U. zu einer Einheit zusammengefasst. (TV-Aufnahmen)
    Ändert sich nun die Startzeit einer Sendung, so wird dies in die "BasisCollection" mit den Einzelsendungen übertragen und die 2. Collection mit den zusammengefassten Sendezeiten wird neu aufgebaut. Mein Ziel ist es jetzt nach dem Wechsel aus der Zelle "Startzeit" in die Nachbarzelle "Endzeit" nach dem Neuaufbau (und der Prüfung auf Fehler / Überschneidungen) wieder in der Zelle "Endzeit" zu stehen. Dann lässt sich diese Zeit (falls gewünscht) auch gleich anpassen.
    Viel Aufwand für etwas zus. Komfort, aber so bin ich halt. :)
    Mit deiner Lösung ist zwar der Code recht aufwendig, aber die Anwendung / der Aufruf für mich sehr einfach.

    Daher nochmal vielen Dank dafür! Das hätte ich bei meinem aktuellen Wissen über WPF nicht geschafft.

    Gruß

    eddi
    Gerne, kein problem.

    Wenn ich das so lese würde ich persönlich aber zur Daten-"anpassung/änderung" aber gar nicht auf ein DataGrid setzen.

    Daten anzeigen und den Datensatz markieren ja. Aber die Datenänderung würd ich über ein DetailView machen.
    Also so das z.b. oben das DataGrid ist und unten eine übersichtliche Anzeige des aktuell gewählten Datensatzes. Und dort ändere ich Daten.

    Beim aufbau der Daten markiere ich wieder den letzten Datensatz und habe unten wieder die Detailansicht. Finde ich persönlich noch viel komfortabler. Aber das ist sicher geschmacksache.

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##