Vytvorenie read-only stĺpca (DataGridLabelColumn) pre WinForms DataGrid

Publikoval Michal Kočí dňa 6.2.2005 o 17:59 v kategórii .Net

Prednedávnom som začal trochu aktívnejšie programovať databázové WinForms aplikácie, s použitím DataGrid-u. Tento je dobre navrhnutý, ale jeho použiteľnosť je v istých momentoch nie moc dobrá. Keďže prvotne som programoval v Delphi a neviem prečo, bol som zvyknutý Grid používať iba na zobrazenie dát, nie na editáciu, tento zvyk som si akosi preniesol aj do terajšej doby, doby kedy programujem pod .Net. Za týmto názorom si stojím aj naďalej, netvrdím, že jeho platnosť je absolútna, ale vo väčšine prípadom mi príjde užívateľsky pohodlenejšie, aby sa im v Gride zobrazovali dáta, ale na editáciu jednotlivých jeho položiek bol použitý samostatný formulár.

Ak DataGrid zobrazuje dáta z DataView, potom jednoducho nastavíte vlastnosti dátového pohľadu AllowDelete, AllowEdit a AllowNew na false a DataGrid Vám dáta zobrazí v režime “read-only”. To je iste skvelé, ale kto to skúsil tak iste zbadal, že ak kliknete do jednotlivých buniek, zaktivuje sa ich TextBox umiestnený v bunke a text v ňom sa tak divne podfarbí. To že sa zaktivuje TextBox má podľa mňa jednu zásadnú výhodu: dáta z bunky môžete skopírovať do schránky. Ale tu výhody podľa mňa končia a túto výhodu dostatočne zakryje otrasne esteticky pôsobiaci výzor bunky.

O tom ako DataGrid postupuje pri renderovaní svojho obsahu bolo už čo to popísané, preto to nebudem rozoberať a opakovať (viď napríklad odkazy na konci tohto príspevku). Určite teda tušíte, že ak chcete v DataGrid-e použiť vlastný výzor, trochu si ho prispôsobiť, musíte si pripraviť DataGridTableStyle a pridať ho do kolekcie TableStyles. Do DataGridTableStyle musíte tiež pridať pre každý stĺpec, ktorý chcete zobraziť práve jeden DataGridColumnStyle - tie pridávate do kolekcie GridColumnStyles. Takže, ak som si chcel pripraviť nový typ stĺpca, musel som zdediť DataGridColumnStyle, ktorý sa mi postará o renderovanie stĺpca, pričom moje požiadavky sa dajú zápísať pomocou zopár krátkych odrážok. Takže, chcel som, aby tento štýl stĺpca

  • zobrazoval dáta v “read-only“ režime, t.j. aby sa dáta nedali modifikovať
  • aby vedel formátovať dáta v ňom zobrazované
  • aby dáta vedel v rámci bunky zarovnávať (vertikálne aj horizontálne)
  • aby v prípade že text presahuje šírku stĺpca, tento text zakončil tromi bodkami

Takže ako som približne postupoval. Prvý krokom je, vytvorenie kostry triedy dediacej z DataGridColumnStyle. Tá definuje zopár abstraktných metód, ktoré treba prepísať, aby sa trieda dala použiť. V skratke teraz zhrniem, ktoré a k čomu použijeme:

  • Abort, Commit a Edit - tieto nebudeme implementovať, pretože nami vytvorený štýl stĺpca nebude slúžiť na modifikáciu dát
  • GetMinimumHeight - implementácia bude len predávať informáciu o tom, akú minimálnu výšku bunky požadujeme. Túto si bide môcť definovať rovno užívateľ, prčom defaultná hodnota bude 20 pixelov
  • GetPreferredHeight - implementácia vypočíta, akú výšku bude mať text, obsiahnutý v bunke
  • GetPreferredSize - implementácia vypočíta, akú šírku a výšku bude mať text, obsiahnutý v bunke
  • Paint - implementácia vykreslí do bunky samotný text naformátovaný podľa požiadaviek na formát bunky

Vrátim sa ešte k mojim požiadavkám. Požiadavku "aby vedel formátovať dáta v ňom zobrazované" som si predstavoval asi tak, že si budem ako programátor môcť zadať, v akom tvare sa majú formátovať celočíselné, decimálne, dátumové a pravdivostné dáta. Keďže som predpokladal, že pre každý typ dát budem požadovať iné predvolené formátovanie textu, pripravil som zopár prístupových členov pre ich zmenu:

  • IntFormat - formát použitý v metóde int.ToString() pre formátovanie celočíselných dát
  • DecimalFormat - formát použitý v metóde decimal.ToString() pre formátovanie decimálnych dát
  • DateTimeFormat - formát, použtý v metóde DateTime.ToString() pre formátovanie dátumových dát
  • BoolNo - text, použitý ak sú dáta pravdivostné a ich hodnota je false
  • BoolYes - text, použitý ak sú dáta pravdivostné a ich hodnota je true
  • FormatProvider - poskytovateľ formátu, použitý v metódach ToString()

Navyše som ešte chcel, aby som si pravdivostné mohol zobrazovať aj ako zaškrtávacie pole (ktoré sa samozrejme tiež nebude dať meniť). Preto som pridal ešte nasledovné prístupové členy:

  • BoolAsCheckBox - true ak sa má vykresliť pravdivostná hodnota aj ako CheckBox, inak false
  • CheckBoxHeight - výška CheckBox-u
  • CheckBoxWidth - šírka CheckBox-u

Aby som povolil zarovnanie dát v bunke a prípadne pridanie troch bodiek, ak sa text do bunky nezmestí, pripravil som aj nasledovné prístupové členy:

  • TextHorizontalAlignment - horizontálne zarovnanie textu
  • TextVerticalAlignment - vertikálne zarovnanie textu
  • TextTrimming - orezávanie textu pretekajúceho bunkou
  • TextNoWrap - či sa má text zalomiť do viacerých riadkov

A teraz zopár slov k inmplementácii metód, ktoré som implementoval. Metódy GetMinimumHeight, GetPreferredHeight a GetPreferredSize som naimplementoval nasledovne:

protected override int GetMinimumHeight()
{
  return minimumHeight;
}
protected override int GetPreferredHeight(Graphics g, 
  object value)
{
  Font f = this.DataGridTableStyle.DataGrid.Font;
  SizeF szf = g.MeasureString(GetDataAsString(value), f, 200, sf);
  return (int) szf.Height;
}
protected override Size GetPreferredSize(Graphics g, 
  object value)
{
  Font f = this.DataGridTableStyle.DataGrid.Font;
  SizeF szf = g.MeasureString(GetDataAsString(value), f, 200, sf);
  return new Size((int) szf.Width, (int) szf.Height);
}

Prvá je jasná, v druhej a tretej metóde si zavolám metódu GetDataAsString (gro celej triedy, jej popis nasleduje nižšie) a vypočítam si, aká bude výška a šírka vykreslovaného textu. No a ako teda vyzerá metóda GetDataAsString? Vyzerá takto:

private string GetDataAsString(object o)
{
  string result = o.ToString();
  if(o is bool)
  {
    result = ((Boolean) o) ? boolYes : boolNo;
  }
  if(o is DateTime)
  {
    result = ((DateTime) o).ToString(dateTimeFormat, formatProvider);
  }
  if(o is decimal)
  {
    result = ((Decimal) o).ToString(decimalFormat, formatProvider);
  }
  if(o is int)
  {
    result = ((Int32) o).ToString(intFormat, formatProvider);
  }
  if(o is string)
  {
    result = ((string) o).Replace(Environment.NewLine, string.Empty);
  }
  return result;
}

Pri celočíselných, decimálnych a dátumových dátach len voláme metódy ToString, ktorým predáme požadované formátovanie a poskytovateľa formátovania. Pri formátovaní reťazcov navyše odstránime zalamovanie riadkov. Toto je potrebné, ak máte v databáze v reťazcovom poli aj zalamovače, pretože tie rozhodia výzor bunky a dáta sa stávajú nečitateľné. Pri pravdivostných hodnotách použijeme nadefinované textové reťazce pre hodnoty true a false. Pre všetky ostatné dátové typy použijeme jednoducho zodpovedajúcu metódu ToString.

Ako som bol spomínal, túto metódu voláme z metód GetPreferredHeight a GetPreferredSize. Navyše ju však aj voláme z metódy Paint, ktorá sa stará o samotné vykreslenie obsahu bunky:

protected override void Paint(Graphics g, Rectangle bounds, 
  CurrencyManager source, int rowNum, Brush backBrush, 
  Brush foreBrush, bool alignToRight)
{
  g.FillRectangle( backBrush, bounds );
  g.DrawString( 
    GetDataAsString(this.GetColumnValueAtRow(source, rowNum)), 
    this.DataGridTableStyle.DataGrid.Font, 
    foreBrush, 
    bounds, 
    sf);
  if((this.GetColumnValueAtRow(source, rowNum) is bool) && 
    (boolAsCheckBox))
  {
    ControlPaint.DrawCheckBox(
      g, 
      (bounds.Width - checkBoxWidth) / 2 + bounds.X, 
      (bounds.Height - checkBoxHeight) / 2 + bounds.Y, 
      checkBoxWidth, 
      checkBoxHeight, 
      (bool) GetColumnValueAtRow(source, rowNum) ?
        ButtonState.Checked : ButtonState.Normal);
  }
}

V prvom kroku prekreslíme obdĺžnik bunky, následne do neho vykreslíme text. Ak máme vykresliť pravdivostnú hodnotu ako CheckBox, použijeme statickú metódu DrawCheckBox triedy ControlPaint. Pre získanie dát, ktoré máme vykresliť zavoláme metódu GetColumnValueAtRow, pre vykreslenie textu metódu DrawString triedy Graphics.

Metóda DrawString ako posledný parameter požaduje objekt triedy StringFormat, ktorým sa dá zabezpečiť práve zarovnanie textu v bunke a podobne, čiže prístupové členy TextHorizontalAlignment, TextVerticalAlignment, TextNoWrap a TextTrimming práve sprístupňujú a nastavujú hodnoty privátnej premennej sf typu StringFormat:

public StringAlignment TextHorizontalAlignment
{
  get { return this.sf.Alignment; }
  set { this.sf.Alignment = value; }
}
public StringAlignment TextVerticalAlignment
{
  get { return this.sf.LineAlignment; }
  set { this.sf.LineAlignment = value; }
}
public bool TextNoWrap
{
  get { return (this.sf.FormatFlags & StringFormatFlags.NoWrap) > 0; }
  set 
  { 
    if(value)
      this.sf.FormatFlags |= StringFormatFlags.NoWrap;
    else
      this.sf.FormatFlags &= ~StringFormatFlags.NoWrap;
  }
}
public StringTrimming TextTriming
{
  get { return this.sf.Trimming; }
  set { this.sf.Trimming = value; }
}

V metóde TextNoWrap používame operátory ~, | a & ako bitové operátory, pretože FormatFlags je Enum, navyše umožňjúci obsahovať viacero hodnôt (vďaka atribútu FlagsAttribute).

Aby bola trieda kompletná, je vhodné ešte v konštruktore nastaviť preddefinované hodnoty privátnych premenných tak, aby sa pri použití museli meniť ich hodnoty len ozaj v nutných prípadoch.

A je to. Ani to nebolo moc zložité a read-only DataGridColumnStyle je na svete. Samozrejme, da sá ešte viac prispôsobiť Vašim potrebám, to však už nechám na Vás...

Užitočné zdroje, z ktorých môžete čerpať ďaľšie informácie:

Prípadne si stiahnite zdrojový kód popisovaného DataGridLabelColumn.

Mohlo by ťa tiež zaujímať

Páčil sa ti príspevok?

Zdieľaj príspevok alebo si ho odlož na neskôr

Sleduj ma

Ak nechceš premeškať príspevky ako je tento, sleduj ma na Twitteri, alebo ak máš RSS čítačku, môžeš sledovať môj RSS kanál.