[c#] Binding Basics - Object in Listview/Gridview

chmee

verstaubtes inventar
Premium-User
Bis Dato hab ich die Objekt-Daten in der Gridview autark per Items.Add() in die Gridview eingetragen (sourcecode ~Zeilen 268+). Nun wird es Zeit, das Eine (List<Object>) an das Andere (Listview mit Gridview) zu binden, weil ich weitere Dinge wie zB Entfernen oder Markieren zum Konvertieren in der Listview zulassen will. Langer Rede kurzer Sinn: Mir will das Binden nur so halb gelingen.

Das Object sieht so aus (verkürzt, hier komplett):
Code:
public class rawList : ObservableCollection<raw>
  {
  }

public class raw
{
  public data data { get; set; }
  public List<Blocks.rawBlock> RAWBlocks { get; set; }
  public List<Blocks.mlvBlock> VIDFBlocks { get; set; }
  public List<Blocks.mlvBlock> AUDFBlocks { get; set; }
}
public class data
{
  public metadata metaData { get; set; }
  public audiodata audioData { get; set; }
  public filedata fileData { get; set; }
}
public class metadata
{
   public string fpsString { get; set; }
   public int whiteBalance { get; set; }
   public bool isMLV { get; set; }
}
public class audiodata
{
    public bool hasAudio { get; set; }
    public int audioSamplingRate { get; set; }
    public int audioChannels { get; set; }
}
public class filedata
{
   public bool convertIt { get; set; }
   public string fileNameOnly { get; set; }
}
Wie Ihr seht, Objekt mit Objekten drin, in eine Liste gefüllt. Ich nutze im Hauptcode eine List<raw>, die ich als Observable<raw> angelegt habe. OneWay funktionierts soweit, aber

(1) Probleme macht mir ein boolean (data.filedata.convertIt), dass ich als Checkbox zeigen möchte und natürlich auch TwoWay im Object ändern.

(2) Abgesehen von anderen booleans, die nur OneWay sind, aber optimalerweise nicht als string dargestellt werden sollen, sondern als SpecialChars (Kreuz und Check), das klingt nach nem Template.

Die XAML sieht momentan so aus (verkürzt):
Code:
<Window x:Class="raw2cdng_v2.MainWindow"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="clr-namespace:raw2cdng_v2"
  xmlns:sys="clr-namespace:System;assembly=mscorlib"
  Title="raw2cdng"
  ResizeMode="NoResize"
  WindowStyle="None"
  mc:Ignorable="d"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  Height="556" Width="917"
  MaxWidth="920" MaxHeight="556" SizeToContent="WidthAndHeight"
  BorderBrush="#FFC8C8C8"
  HorizontalAlignment="Left" VerticalAlignment="Top"
  BorderThickness="1"
  MouseLeftButtonDown="Window_MouseLeftButtonDown"
  Icon="/raw2cdng_v2;component/appicon.ico">

  <Window.Resources>

  <ObjectDataProvider x:Key="rawFilesList" ObjectType="{x:Type local:rawList}" />

<Style x:Key="chmeeListview" TargetType="{x:Type ListViewItem}">
  <Style.Resources>
  <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="White" />
  <SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="White" />
  <SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="Black" />
  <SolidColorBrush x:Key="{x:Static SystemColors.ControlTextBrushKey}" Color="Black" />
  </Style.Resources>
  <Setter Property="Focusable" Value="False" />
  </Style>
  </Window.Resources>
Das also der "obere" Teil. Hier die Listview.
Code:
<Grid Width="894" HorizontalAlignment="Left" VerticalAlignment="Top" Height="515">
  <ListView ItemsSource="{Binding}" Height="400" HorizontalAlignment="Left" Margin="8,71,0,0" Name="_batchList" VerticalAlignment="Top" Width="550" Drop="batchList_Drop" AllowDrop="True" PreviewMouseLeftButtonUp="batchList_Click" SelectionMode="Extended" IsTextSearchEnabled="False" HorizontalContentAlignment="Center" DataContext="Resource_of_rawFiles">
  <ListView.View>
  <GridView>
  <GridViewColumn Header="do" Width="22">
  <GridViewColumn.CellTemplate>
  <DataTemplate>
  <CheckBox Margin="-4,0,-4,0" IsChecked="{Binding Path=data.filedata.convertIt,Mode=TwoWay}" Checked="convert_Checked" Unchecked="convert_Unchecked" DataContext="{Binding data.filedata.convertIt}"/>
  </DataTemplate>
  </GridViewColumn.CellTemplate>
  </GridViewColumn>
  <GridViewColumn Header="type" Width="36" DisplayMemberBinding="{Binding Path=data.metaData.isMLV}" />
  <GridViewColumn Header="filename" Width="120" DisplayMemberBinding="{Binding Path=data.fileData.fileNameOnly}"/>
  <GridViewColumn Header="fps" Width="50" DisplayMemberBinding="{Binding Path=data.metaData.fpsString}"/>
  <GridViewColumn Header="audio" Width="36" DisplayMemberBinding="{Binding Path=data.metaData.hasAudio}"/>
  </GridView>
  </ListView.View>
  </ListView>
</Grid>
Ich bin Durcheinander :D
Databinding in XAML, ItemsSource oder DataContext?
Wie packe ich die booleans in die reflektierte Checkbox? (convertIt)
Wie ändere ich boolean true/false in andere Chars (isMLV als RAW oder MLV)?

Hier noch Exzerpte aus dem MainCode:
Code:
public rawList rawFiles = new rawList();

  // daten auslesen, Object füllen
  // und dann aus nem Subthread in die Liste packen
  this.Dispatcher.Invoke((Action)(() =>
  {
  rawFiles.Add(importRaw);
  }));

Nun, ich stecke einfach fest, weil ich das Konzept des Bindings scheinbar noch nicht kapiert hab. Danke für all die Hilfe, die da kommen mag :)

mfg chmee
 
Zuletzt bearbeitet:
ich sehe die Aufrufe, aber fehlende Antworten. Ich probiers nochmal mit weniger Text:

* OneWay, also reine Ausgabe der Objektdaten funktioniert, wenn auch nur als .toString().

* Gegeben ist eine Liste von Objekt raw (welches auch aus Objekten besteht), die dynamisch erstellt wird.
* jene soll standesgemäß in einer Gridview dargestellt werden, an einigen Stellen mit einer Rückkopplung auf das Objekt (zB read/write boolean)
* Schönheitskorrekturen bei Objekten, die als .toString() wenig Sinn machen (zB boolean), zB in Form von Checkbox oder Ersatzzeichen.

Ich habe schon recht viele Sachen im Netz gelesen, aber das Verständnis fehlt, wann ItemsSource, wann Datacontext (das Gleiche?), wie (anders) verhält sich ein Observable ggüber dem normalen Objekt, ist ein INotify nötig oder ist es inherited in Observable?

mfg chmee
 
Etwas verspätet aber lass mich mal versuchen etwas Licht ins Dunkel zu bringen.

Datacontext und ItemsSource haben eigentlich überhaupt nichts miteinander zu tun.

Mit Datacontext bestimmst du welches Objekt (genauer eigentlich Objekt-Typ) die Eigenschaften für deine Bindings bereit stellt.
Dein visueller Layer (xaml) ist völlig unabhängig von deinem Daten Layer und nur dafür da die Daten auf eine Benutzerfreundliche Art darzustellen.
Damit dieser visuelle Layer jetzt weiss worauf sich deine Bindings beziehen musst du den Datacontext setzen.
Der Datacontext wird dabei übrigens nach unten vererbt (solange er nicht überschrieben wird logischerweise).

Angenommen ich habe nun also folgende MainWindow.xaml
XML:
<Window x:Class="WpftestApplication1.MainWindow"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="MainWindow"
  mc:Ignorable="d"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  d:DesignHeight="350" d:DesignWidth="525"
  x:Name="MainWin"
  DataContext="{Binding RelativeSource={RelativeSource Self}}"
  >
 
  <StackPanel Orientation="Vertical">

  <TextBlock Text="{Binding MyClassInstance}" />

  </StackPanel>
 
</Window>

Und die dazugehörige Code-Behind Datei (MainWindow.xaml.cs)
C#:
using System.Windows;

namespace WpftestApplication1
{
  public partial class MainWindow : Window
  {
     public MainWindow()
     {
        InitializeComponent();  
     }

     private MyClass myClassInstance = new MyClass();
     public MyClass MyClassInstance
     {
        get { return myClassInstance; }
        set { myClassInstance = value; }
     }
  }


  public class MyClass
  {
     private int myIntProperty = 123;
     public int MyIntProperty
     {
        get { return myIntProperty; }
        set { myIntProperty = value; }
     }
  }
  
}

Durch DataContext="{Binding RelativeSource={RelativeSource Self}}" im xaml setze ich den DataContext auf den Typen MainWindow (MainWindow.xaml.cs).
Dieser Datacontext wird nach unten vererbt. Also haben StackPanel und TextBlock den gleichen DataContext.
Alle Bindings beziehen sich nun als auf das Objekt (bzw. den Typen) MainWindow.
Das TextBlock Binding {Binding MyClassInstance} ist also nicht TextBlock.MyClassInstance sonder TextBlock.DataContext.MyClassInstance. Durch den gesetzten DataContext wird das aufgelöst nach MainWindow.MyClassInstance.
Wenn ich nun die Eigenschaft MyIntProperty in meinem TextBlock anzeigen möcht kann ich folgendes machen:
XML:
<TextBlock Text="{Binding MyClassInstance.MyIntProperty}" />
Falls MyClass jetzt aber mehrere Eigenschaften hat welche ich anzeigen lassen will und ich keine Lust hab jedes mal MyClassInstance. in meinen Bindings zu schreiben könnte ich aber auch folgendes machen:
XML:
<StackPanel Orientation="Vertical" DataContext="{Binding MyClassInstance}">

  <TextBlock Text="{Binding MyIntProperty}" />

  <TextBlock Text="{Binding MyStringProperty}" />

  </StackPanel>

MyClass sieht jetzt logischerweise so aus
C#:
public class MyClass
  {
  private int myIntProperty = 123;
  public int MyIntProperty
  {
  get { return myIntProperty; }
  set { myIntProperty = value; }
  }

  private string myStringProperty = "Im a string";
  public string MyStringProperty
  {
  get { return myStringProperty; }
  set { myStringProperty = value; }
  }

  }


Nun zur ItemsSource.
Mit der ItemsSource gebe ich einer ItemsControl (oder abgeleitet) ein IENumerable.
Alle im IENumerable enthaltenen Objekte werden von der ItemsControl verwendet um den Inhalt zu generieren.

MainWindow.xaml.cs
XML:
<Window x:Class="WpftestApplication1.MainWindow"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="MainWindow"
  mc:Ignorable="d"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  d:DesignHeight="350" d:DesignWidth="525"
  x:Name="MainWin"
  DataContext="{Binding RelativeSource={RelativeSource Self}}"
  >

  <Grid>

  <DataGrid ItemsSource="{Binding MyClassList}">
  
  </DataGrid>
  
  </Grid>
  
</Window>

MainWindow.xaml.cs
C#:
using System.Windows;
using System.Linq;
using System.Collections.Generic;

namespace WpftestApplication1
{
  public partial class MainWindow : Window
  {
  public MainWindow()
  {
  this.initializeMyClassList();
  InitializeComponent();  
  }

  private void initializeMyClassList()
  {
  List<MyClass> lst = new List<MyClass>()
  {
  new MyClass(){ MyIntProperty = 1, MyStringProperty = "myClass1", MyBoolProeprty = true},
  new MyClass(){ MyIntProperty = 2, MyStringProperty = "myClass2", MyBoolProeprty = false},
  new MyClass(){ MyIntProperty = 3, MyStringProperty = "myClass3", MyBoolProeprty = true}
  };

  this.myClassList = lst;
  }

  private IEnumerable<MyClass> myClassList;
  public IEnumerable<MyClass> MyClassList
  {
  get { return myClassList; }
  set { myClassList = value; }
  }

  
  }


  public class MyClass
  {
  private int myIntProperty;
  public int MyIntProperty
  {
  get { return myIntProperty; }
  set { myIntProperty = value; }
  }

  private string myStringProperty;
  public string MyStringProperty
  {
  get { return myStringProperty; }
  set { myStringProperty = value; }
  }

  private bool myBoolProperty;
  public bool MyBoolProeprty
  {
  get { return myBoolProperty; }
  set { myBoolProperty = value; }
  }


  }
  
}

Das ItemsSource Binding des DataGrid bewirkt jetzt vereinfacht in PseudoCode ausgedrückt also folgendes
Code:
foreach object o in this.ItemsSource
{
  DataGridRow row = new DataGridRow
  row.DataContext = o
  foreach Property p in row.DataContext.Properties
  {
     DataGridCell cell = new DataGridCell
     cell.Content = p
     row.Cells.Add(cell)
  }
  this.Rows.Add(row)
}

Wie gesagt...stark vereinfacht.... aber das Prinzip von ItemsSource sollte jetzt klar sein.
Anhand des ItemTemplate/ItemTemplateSelector oder generell DataTemplates wird dann entschieden wie genau der Inhalt der Zelle dargestellt werden soll.

Jetzt zum Observable.
Wenn du in WPF ein Binding auf eine Eigenschaft erstellst dann versucht WPF den INotify (INotifyPropertyChanged, INotifyCollectionCanged etc.) Events der DataContext Klasse zu lauschen damit Änderungen im Daten Layer auch im Visuellen Layer übernommen werden.
Das bedeutet jetzt folgendes (siehe nochmal oberes Beispiel mit dem StackPanel/TextBlock)
Ändere ich zur Laufzeit den Wert von MyIntProperty von 123 auf 456 wird sich am Inhalt des TextBlock überhaupt nichts ändern weil MyClass nicht die nötigen INotify Events bereitstellt damit WPF merkt das sich überhaupt was geändert hat.
Änder ich MyClass jetzt aber ab:
C#:
public class MyClass : INotifyPropertyChanged
  {
  private int myIntProperty;
  public int MyIntProperty
  {
  get { return myIntProperty; }
  set
  {
  if (value == myIntProperty)
  return;

  myIntProperty = value;
  this.OnNotifyPropertyChanged("MyIntProperty");
  }
  }

  private string myStringProperty;
  public string MyStringProperty
  {
  get { return myStringProperty; }
  set { myStringProperty = value; }
  }

  private bool myBoolProperty;
  public bool MyBoolProeprty
  {
  get { return myBoolProperty; }
  set { myBoolProperty = value; }
  }
  
  public event PropertyChangedEventHandler PropertyChanged;

  private void OnNotifyPropertyChanged(string property)
  {
  if (this.PropertyChanged != null)
  this.PropertyChanged(this, new PropertyChangedEventArgs(property));
  }
  }
Wenn ich jetzt zur Laufzeit den Wert von MyIntProperty ändere wird das PropertyChanged event ausgelöst.
WPF lauscht diesem Event und wird den neuen Wert abfragen und im TextBlock anzeigen.
Das gleiche gilt logischerweise für ObservableCollections.
Wenn du willst das Änderungen an der Collection (child add, remove etc.) im xaml (z.b. im DataGrid)
reflektiert werden musst du an eine ObservableCollection binden.
Aber Achtung: ObservableCollection informiert nur über Änderungen an der Collection und nicht über Änderungen der objekte in der Collection.
Ändert sich also eine Eigenschaft eines Objektes in einer ObservableCollection ist ihr das völlig egal.
Ändert sich die Collection an sich wird das INotifyCollectionChanged Event ausgelöst.

Damit die Werte in einem TwoWay Binding vom xaml in deinen DatenLayer übernommen werden sind übrigens keine INotify Events nötig. Das geht immer. Einzige voraussetzung ist wie gesagt ein TwoWay oder OneWayToSource Binding.
 
Zurück