none
Fragen zum DataGridView RRS feed

  • Frage

  • Hallo,

    ich bin gerade dabei, ein Programm zu erstellen, welches meine DVDs verwaltet. Dazu verwende ich XML-Dateien und ein DataGridView, welches an ein Objekt gebunden ist. Hier folgt ein Ausschnitt meines Programmes mit der Dvd-Klasse, der DvdCollection-Klasse und dem Formular Form1:

    Imports System.Collections.ObjectModel
    
    <System.Xml.Serialization.XmlRoot(ElementName:="dvd", DataType:="string")> _
    Public Class Dvd
    
        <System.Xml.Serialization.XmlElement(ElementName:="title", DataType:="string")> _
        Public Property Title() As String
    
        <System.Xml.Serialization.XmlElement(ElementName:="desc", DataType:="string")> _
        Public Property Description() As String
    
        <System.Xml.Serialization.XmlElement(ElementName:="age_rating", DataType:="int")> _
        Public Property AgeRating() As Integer
    
        <System.Xml.Serialization.XmlArray(ElementName:="actors")> _
        <System.Xml.Serialization.XmlArrayItem(ElementName:="actor", DataType:="string")> _
        Public Property Actors() As Collection(Of String)
    
    End Class
    Imports System.IO
    Imports System.Linq
    Imports System.ComponentModel
    
    Public Class DvdCollection
        Inherits BindingList(Of Dvd)
    
        Public Sub LoadDvds()
            Dim dir As New DirectoryInfo("D:\My Documents")
            Dim fileList As FileInfo() = dir.GetFiles("*.xml", SearchOption.TopDirectoryOnly)
            Dim fileQuery = From file In fileList _
                            Where file.Name.StartsWith("DVD") _
                            Order By file.Name _
                            Select file
            ' Load xml files
            Dim dvd As Dvd
            For Each file As FileInfo In fileQuery
                dvd = XmlFile(Of Dvd).Load(file.FullName)
                Me.Add(dvd)
            Next
        End Sub
    
    End Class
    Public Class Form1
    
        Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
            Dim dvdCollection As New DvdCollection
            dvdCollection.LoadDvds()
            ' Fill master DataGridView
            Me.BindingSource1.DataSource = dvdCollection
            Me.DataGridView1.DataSource = Me.BindingSource1
            Me.DataGridView1.Columns.Remove("Description")
            ' Fill detail DataGridView
            Me.BindingSource2.DataSource = Me.BindingSource1
            Me.BindingSource2.DataMember = "Actors"
            Me.DataGridView2.DataSource = Me.BindingSource2
        End Sub
    
        Private Sub DataGridView1_SelectionChanged(sender As Object, e As System.EventArgs) Handles DataGridView1.SelectionChanged
            Dim selDvd As Dvd = TryCast(Me.BindingSource1.Current, Dvd)
            If selDvd IsNot Nothing Then
                Me.RichTextBox1.Text = selDvd.Description
            End If
        End Sub
    
    End Class

    Nun kommen meine Fragen:

    1. Wie kann ich statt der Zahl in der Spalte AgeRating ein entsprechendes Bild aus der Resource darstellen? Mir ist bekannt, dass ich bei einem ungebundenen DGV das CellFormatting Ereignis behandeln kann. Aber bei einem gebundenen DGV?
    2. Ich möchte ausgewählte Zeilen aus dem DGV löschen. Dabei sollen dann auch die dazugehörigen XML-Dateien und Einträge aus der BindingList gelöscht werden. Wie könnte ich das implementieren?
    3. Ich habe ein Master-Detail DGV über BindingSources gekoppelt. Leider wird meine Actors Collection im Detail-DGV nicht richtig dargestellt. Statt der Namen erhalte ich die Stringlänge angezeigt (s. Bild). Ich habe keinen blassen Schimmer warum.

    Für Testzwecke füge ich auch eine XML-Datei (DVD001.xml) bei:

    <?xml version="1.0" encoding="utf-8"?>
    <dvd xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
       <title>Try card</title>
       <desc>Bla Bla 1</desc>
       <age_rating>16</age_rating>
       <actors>
          <actor>Bruce Factor</actor>
          <actor>Hint Willis</actor>
       </actors>
    </dvd>

    Ist jemand bereit, sich o.g. Fragen anzunehmen?

    EDIT: Die Klasse XmlFile(of T) ist in diesem Thread zu finden.

    Schöne Grüße,

    LittleBlueBird





    • Bearbeitet LittleBlueBird Dienstag, 17. Juli 2012 20:55 Link zur XmlFile Klasse hinzugefügt
    Montag, 16. Juli 2012 10:25

Antworten

  • Hallo LittleBlueBird,

    irgendwie ist mir dieser Thread dauernd "durchgeflutscht"...
    Da Du das meiste bereits selbst gefunden hast, nur zur Anzeige von Grafiken.

    Im Prinzip kannst Du den gleichen Weg wie beim Dateinamen einschlagen.
    Damit die Grafik zur aktuellen Alterseingabe passt solltest Du die INotifyPropertyChanged Schnittstelle implementieren.

    <XmlRoot(ElementName:="dvd")>
    Public Class Dvd
        Implements System.ComponentModel.INotifyPropertyChanged ' Für Änderungsbenachrichtigungen
    
        Private _title As String
        Private _description As String
        Private _ageRating As Integer
    
        ' Grafiken aus den Ressourcen 
        Private Shared Fsk6Image As Image = My.Resources.Fsk6
        Private Shared Fsk12Image As Image = My.Resources.Fsk12
        Private Shared Fsk16Image As Image = My.Resources.Fsk16
        Private Shared Fsk18Image As Image = My.Resources.Fsk18
    
        <XmlElement(ElementName:="title")>
        <DisplayName("Titel")>
        Public Property Title() As String
            Get
                Return Me._title
            End Get
            Set(value As String)
                If Me._title <> value Then
                    Me._title = value
                    OnPropertyChanged("Title")
                End If
            End Set
        End Property
    
        <XmlElement(ElementName:="desc")>
        <DisplayName("Beschreibung")>
        Public Property Description() As String
            Get
                Return Me._description
            End Get
            Set(value As String)
                If Me._description <> value Then
                    Me._description = value
                    OnPropertyChanged("Description")
                End If
            End Set
        End Property
    
        <XmlElement(ElementName:="age_rating")>
        <DisplayName("Altersfreigabe")>
        Public Property AgeRating() As Integer
            Get
                Return Me._ageRating
            End Get
            Set(value As Integer)
                If Me._ageRating <> value Then
                    Me._ageRating = value
                    OnPropertyChanged("AgeRating")
                    OnPropertyChanged("AgeRatingImage") ' Bild aktualisieren
                End If
            End Set
        End Property
    
        <XmlIgnore()>
        <DisplayName("FSK")>
        Public ReadOnly Property FskImage As Image
            Get
                Select Case True
                    Case _ageRating <= 6
                        Return Fsk6Image
                    Case _ageRating <= 12
                        Return Fsk12Image
                    Case _ageRating <= 16
                        Return Fsk16Image
                    Case Else
                        Return Fsk18Image
                End Select
            End Get
        End Property
    
        Protected Overridable Sub OnPropertyChanged(propertyName As String)
            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
        End Sub
    
        Public Event PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
    End Class
    

    Ich habe die Actors mal weggelassen, da hier (zunächst) nicht von Interesse.
    Für die Grafiken verwende ich gemeinsame Ressourcen (nicht unbedingt vollständig).
    Über die Benachrichtigung beim Ändern des Alters wird dann die Grafik synchron gehalten.

    Als Nebenfeature habe ich das DisplayNameAttribute eingefügt, damit die Überschriften in Deutsch sind.

    Gruß Elmar

    Freitag, 20. Juli 2012 09:16
  • Hallo,

    IRaiseItemChangedEvents funktioniert nur mit INotifyPropertyChanged zusammen,
    denn sie bewirkt, dass die BindingList sich in das Ereignis einklinkt.

    zu 1.)
    Wenn Du für die Eigenschaft das Attribut Browsable(false) verwendest, so wird sie nicht angezeigt -
    das gilt dann aber für alle Bindungen.
    Willst Du die Eigenschaft nur im DataGridView ausblenden, solltest Du die Spalten bearbeiten.

    zu 2.)
    Das kann man schon machen in dem sich eine lokalisierbare Version von DisplayName erstellt
    und die DisplayName Eigenschaft überschreibt, so dass die Daten aus einer Ressource kommen:

    <AttributeUsage(AttributeTargets.[Class] Or AttributeTargets.Method Or AttributeTargets.[Property] Or AttributeTargets.[Event])>
    Public Class SRDisplayNameAttribute
        Inherits DisplayNameAttribute
    
        Public Sub New(displayName As String)
            MyBase.New(displayName)
        End Sub
    
        Private _replaced As Boolean = False
    
        Public Overrides ReadOnly Property DisplayName As String
            Get
                If Not _replaced Then
                    _replaced = True
                    ' Aus einer Resource Datei ersetzen 
                    ' DisplayNameValue enthält anfangs die Original-Zeichenfolge, danach den Ersatz
                    MyBase.DisplayNameValue = strings.ResourceManager.GetString(MyBase.DisplayName)
                End If
                Return MyBase.DisplayName
            End Get
        End Property
    End Class
    

    (gleiches macht im übrigen Microsoft bei der Lokalisierung für Eigenschaften in VS).

    zu 3.) Verwende das DataGridView.CellToolTipTextNeeded-Ereignis

    Was die Eigenschaftsnamen angeht: Da habe ich einfach phantasiert und die deutsche FSK herangezogen,
    (und für die Bildchen schnell gemalte Zahlen - sehen scheußlich aus ;-)
    Wie Du richtig erkannt hast, muss der Text für OnPropertyChanged zur Eigenschaft passen.
    Da mir das verspätet eingefallen ist, habe ich die Umbenennung übersehen.

    Gruß Elmar

    Freitag, 20. Juli 2012 20:04

Alle Antworten

  • Hallo,

    auf Frage 3 habe ich selbst eine Antwort gefunden. Ich habe die Eigenschaft Actors der Klasse Dvd modifiziert,

        <System.Xml.Serialization.XmlArray(ElementName:="actors")> _
        <System.Xml.Serialization.XmlArrayItem(ElementName:="actor", Type:=GetType(Actor))> _
        Public Property Actors() As Collection(Of Actor)

    wobei die Klasse Actor wie folgt aussieht:

    Public Class Actor
    
        <System.Xml.Serialization.XmlTextAttribute(DataType:="string")> _
        Public Property Name As String
    
    End Class

    Dann klappt es auch mit dem Detail-DGV.

    Schöne Grüße,

    LittleBlueBird

    Dienstag, 17. Juli 2012 17:29
  • Hallo,

    auf Frage 2 habe ich auch eine Antwort gefunden. Ich füge der Klasse Dvd eine weitere Eigenschaft FileName hinzu und kennzeichne diese mit XmlIgnore wie folgt:

        <System.Xml.Serialization.XmlIgnore()> _
        Public Property FileName As String

    Damit wird die Eigenschaft beim Serialisieren ignoriert, ich habe aber die Möglichkeit, in der Klasse DvdCollection beim Einlesen der Xml-Dateien die Eingenschaft zu setzen:

    Dim dvd As Dvd = Nothing
    Dim fileName as String = String.Empty
    For Each file As FileInfo In fileQuery
        fileName = file.FullName
        dvd = XmlFile(Of Dvd).Load(fileName)
        dvd.FileName = fileName
        Me.Add(dvd)
    Next

    Im Formular muss ich noch in der Methode Form1_Load folgende Zeile einfügen:

    Me.DataGridView1.Columns.Remove("FileName")

    Eine Lösung für Frage 1 habe ich bis dato nicht gefunden. Vielleicht kann mir doch jemand einen Hinweis geben.

    Schöne Grüße,

    LittleBlueBird

    Mittwoch, 18. Juli 2012 16:44
  • Hallo,

    ich habe bzgl. Frage 1 nach wie vor keine konkrete Lösung. Den Wert durch das Bild zu ersetzen bringt ja nichts, da ich in eine TextBox-Spalte kein Bild einfügen kann. Nach langer Suche und Überlegung bin ich auf folgende Möglichkeiten gekommen:

    1. Ich füge eine zusätzliche ungebundene Image-Spalte ein, die in Abhängigkeit der AgeRating-Spalte die entsprechenden Bilder zeigt. Allerdings habe ich dann zwei Spalten (AgeRating und die Bild-Spalte). Die AgeRating-Spalte kann ich aber nicht entfernen, da ich sonst die Bild-Spalte nicht generieren kann. Was nun?
    2. Ich erstelle eine neue Klasse DvdImage, die sich von der Klasse Dvd lediglich bzgl. der Eigenschaft AgeRating unterscheidet. Diese würde dann nicht vom Typ Integer sondern vom Typ System.Drawing.Bitmap sein. In der DvdCollection Klasse würde ich dann nach dem Deserialisieren das Mapping von Dvd zu DvdImage machen. Die Dvd-Objekte muss ich aber immer mitführen (eigentlich eine Verschwendung von Speicher), da ich sie für das Serialisieren benötige.

    Hat einer der Profis unter Euch eine bessere Idee?

    Ich habe zudem noch eine Vereinfachung für Form1 gefunden. Statt das Ereignis SelectionChanged des DGV zu behandeln, binde ich die RichtTextBox an die Spalte "Description" von BindingSource1:

    Me.RichTextBox1.DataBindings.Add(New Binding("Text", Me.BindingSource1, "Description", True))

    Da ich in meiner letzten Antwort die Routine zum Löschen ausgewählter Zeilen und der entsprechenden Dateien nicht aufgeführt habe, folgt sie jetzt:

    Private Sub DeleteToolStripMenuItem_Click(sender As System.Object, e As System.EventArgs) Handles DeleteToolStripMenuItem.Click
        For Each selectedRow As DataGridViewRow In Me.DataGridView1.SelectedRows
            File.Delete(CType(selectedRow.DataBoundItem, Dvd).FileName)
            dvdCollection.RemoveAt(selectedRow.Index)
        Next
    End Sub

    Schönen Gruß,

    LittleBlueBird

    • Bearbeitet LittleBlueBird Donnerstag, 19. Juli 2012 18:18 Ergänzung hinzugefügt
    Donnerstag, 19. Juli 2012 16:34
  • Hallo LittleBlueBird,

    irgendwie ist mir dieser Thread dauernd "durchgeflutscht"...
    Da Du das meiste bereits selbst gefunden hast, nur zur Anzeige von Grafiken.

    Im Prinzip kannst Du den gleichen Weg wie beim Dateinamen einschlagen.
    Damit die Grafik zur aktuellen Alterseingabe passt solltest Du die INotifyPropertyChanged Schnittstelle implementieren.

    <XmlRoot(ElementName:="dvd")>
    Public Class Dvd
        Implements System.ComponentModel.INotifyPropertyChanged ' Für Änderungsbenachrichtigungen
    
        Private _title As String
        Private _description As String
        Private _ageRating As Integer
    
        ' Grafiken aus den Ressourcen 
        Private Shared Fsk6Image As Image = My.Resources.Fsk6
        Private Shared Fsk12Image As Image = My.Resources.Fsk12
        Private Shared Fsk16Image As Image = My.Resources.Fsk16
        Private Shared Fsk18Image As Image = My.Resources.Fsk18
    
        <XmlElement(ElementName:="title")>
        <DisplayName("Titel")>
        Public Property Title() As String
            Get
                Return Me._title
            End Get
            Set(value As String)
                If Me._title <> value Then
                    Me._title = value
                    OnPropertyChanged("Title")
                End If
            End Set
        End Property
    
        <XmlElement(ElementName:="desc")>
        <DisplayName("Beschreibung")>
        Public Property Description() As String
            Get
                Return Me._description
            End Get
            Set(value As String)
                If Me._description <> value Then
                    Me._description = value
                    OnPropertyChanged("Description")
                End If
            End Set
        End Property
    
        <XmlElement(ElementName:="age_rating")>
        <DisplayName("Altersfreigabe")>
        Public Property AgeRating() As Integer
            Get
                Return Me._ageRating
            End Get
            Set(value As Integer)
                If Me._ageRating <> value Then
                    Me._ageRating = value
                    OnPropertyChanged("AgeRating")
                    OnPropertyChanged("AgeRatingImage") ' Bild aktualisieren
                End If
            End Set
        End Property
    
        <XmlIgnore()>
        <DisplayName("FSK")>
        Public ReadOnly Property FskImage As Image
            Get
                Select Case True
                    Case _ageRating <= 6
                        Return Fsk6Image
                    Case _ageRating <= 12
                        Return Fsk12Image
                    Case _ageRating <= 16
                        Return Fsk16Image
                    Case Else
                        Return Fsk18Image
                End Select
            End Get
        End Property
    
        Protected Overridable Sub OnPropertyChanged(propertyName As String)
            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
        End Sub
    
        Public Event PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
    End Class
    

    Ich habe die Actors mal weggelassen, da hier (zunächst) nicht von Interesse.
    Für die Grafiken verwende ich gemeinsame Ressourcen (nicht unbedingt vollständig).
    Über die Benachrichtigung beim Ändern des Alters wird dann die Grafik synchron gehalten.

    Als Nebenfeature habe ich das DisplayNameAttribute eingefügt, damit die Überschriften in Deutsch sind.

    Gruß Elmar

    Freitag, 20. Juli 2012 09:16
  • Hallo Elmar,

    ich bedanke mich ganz herzlich für Dein Feedback. Deine Lösung ist super elegant. Darauf wäre ich nicht gekommen, wobei sie einleuchtend ist.

    Hinsichtlich der Schnittstelle INotifyPropertyChanged dachte ich, dass sie nicht notwendig sein würde, wenn die Klasse DvdCollection von der BindingList(Of Dvd) abgeleitet wird, weil diese ja die Schnittstelle IRaiseItemChangedEvents implementiert.

    Ich habe noch zusätzliche Fragen:

    1. Ist es möglich, die Eigenschaft Description in der Dvd Klasse mit einem Attribut zu kennzeichnen, damit sie im DGV nicht dargestellt wird? Ich habe es zwar mit BindableAttribute(False) versucht, hat aber nichts gebracht. Außerdem ist die Eigenschaft Description an RichTextBox1 gebunden, also kann ich BindableAttribute schon gar nicht verwenden. Andere Attribute habe ich in der Hilfe nicht gefunden.
    2. Ich habe versucht, mittels DisplayNameAttribute den Spaltenkopf des DGV zu lokalisieren. Das funktioniert aber nicht, da die DisplayNameAttribute Klasse eine Konstante als Argument erwartet. Eine String-Resource ist jedoch eine Variable. Wie läßt sich das lösen?
    3. Wie komme ich an die ToolTips der einzelnen Zellen bei einem gebundenen DGV? Ich würde gerne bei der Bild-Spalte die Zahlen auch als ToolTip darstellen.

    Ich habe noch eine kleine Anmerkung zu Deinem Code: Die Eigenschaft FskImage müsste in AgeRatingImage umbenannt werden, oder aber die Methode OnPropertyChanged müsste "FskImage" als Argument erhalten, sonst klappt es mit der Benachrichtigung für diese Eigenschaft nicht.

    Schönen Gruß,

    LittleBlueBird

    Freitag, 20. Juli 2012 18:43
  • Hallo,

    IRaiseItemChangedEvents funktioniert nur mit INotifyPropertyChanged zusammen,
    denn sie bewirkt, dass die BindingList sich in das Ereignis einklinkt.

    zu 1.)
    Wenn Du für die Eigenschaft das Attribut Browsable(false) verwendest, so wird sie nicht angezeigt -
    das gilt dann aber für alle Bindungen.
    Willst Du die Eigenschaft nur im DataGridView ausblenden, solltest Du die Spalten bearbeiten.

    zu 2.)
    Das kann man schon machen in dem sich eine lokalisierbare Version von DisplayName erstellt
    und die DisplayName Eigenschaft überschreibt, so dass die Daten aus einer Ressource kommen:

    <AttributeUsage(AttributeTargets.[Class] Or AttributeTargets.Method Or AttributeTargets.[Property] Or AttributeTargets.[Event])>
    Public Class SRDisplayNameAttribute
        Inherits DisplayNameAttribute
    
        Public Sub New(displayName As String)
            MyBase.New(displayName)
        End Sub
    
        Private _replaced As Boolean = False
    
        Public Overrides ReadOnly Property DisplayName As String
            Get
                If Not _replaced Then
                    _replaced = True
                    ' Aus einer Resource Datei ersetzen 
                    ' DisplayNameValue enthält anfangs die Original-Zeichenfolge, danach den Ersatz
                    MyBase.DisplayNameValue = strings.ResourceManager.GetString(MyBase.DisplayName)
                End If
                Return MyBase.DisplayName
            End Get
        End Property
    End Class
    

    (gleiches macht im übrigen Microsoft bei der Lokalisierung für Eigenschaften in VS).

    zu 3.) Verwende das DataGridView.CellToolTipTextNeeded-Ereignis

    Was die Eigenschaftsnamen angeht: Da habe ich einfach phantasiert und die deutsche FSK herangezogen,
    (und für die Bildchen schnell gemalte Zahlen - sehen scheußlich aus ;-)
    Wie Du richtig erkannt hast, muss der Text für OnPropertyChanged zur Eigenschaft passen.
    Da mir das verspätet eingefallen ist, habe ich die Umbenennung übersehen.

    Gruß Elmar

    Freitag, 20. Juli 2012 20:04
  • Hallo Elmar,

    ich kann Dir nicht oft genug danken für die Hilfestellung, die Du mir und natürlich auch der Allgemeinheit im Forum leistest.

    zu 1) Ja, ich wollte die Eigenschaft "Description" nur im DGV ausblenden, da ich sie an die RichTextBox1 gebunden habe.

    zu 2) Wenn ich das richtig verstanden habe, ist "strings" nur der Platzhalter für die Resourcendatei, die die Übersetzungen enthält. In meinem Fall ist das "My.Resources".

    zu 3) Ok, ich habe gehofft, man könnte das bei der Bindung irgendwie angeben.

    Meine Bildchen stammen von der FSK. Ich habe sie mit Paint.NET auf 16px verkleinert. ;-)

    Schöne Grüße,

    LittleBlueBird

    Freitag, 20. Juli 2012 21:18
  • Hallo LittleBlueBird,

    zu 1) die automatische Generierung des DataGridView ist zwar für schnelle Ergebnisse brauchbar,
    für ein "richtiges" Layout empfiehlt es sich, die Spalten manuell zu konfigurieren.
    Ergo: Lasse unnötige Attribute weg und stelle es passend ein.

    zu 2) Ja, Strings ist eine Ressourcendatei (resx).
    My.Resources tut es dafür auch, solange die Geschichte überschaubar bleibt.

    zu 3) Ein Ereignis ist hier  das universellste, das für alle Inhalte passt.
    Da hier nur einige wenige Möglichkeiten vorkommen, sollte es überschaubar bleiben.

    Gruß Elmar

    Samstag, 21. Juli 2012 09:20