Hallo zusammen,
Topic
ich habe ein eigenes datengebundenes Label erstellt, das mir (u.a.) erlauben soll, Werte einer Datenbindung formatiert darzustellen und die Datenbindung auf einen übergeordneten IDataContainer zusetzen, aus dessen ggf. mehreren BindingSources ich über die Properties "TableName" und "ColumnName" den genaue Datenbezug spezifizieren kann:
/*———————————————————————————————————————————————————————————————————————————————————————————————————————————————————*/
/* DBLabel */
/*———————————————————————————————————————————————————————————————————————————————————————————————————————————————————*/
/* A Label inheritant providing data features for use with the Casa Application Framework (CAF) */
/*———————————————————————————————————————————————————————————————————————————————————————————————————————————————————*/
// Created : Sept. 2006, cr
// Modified :
/*———————————————————————————————————————————————————————————————————————————————————————————————————————————————————*/
using System;
using System.Drawing;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.ComponentModel;
namespace CCL.Common
{
// DBLabel
/// <summary>
/// A Label inheritant providing data features for use with the Casa Application Framework (CAF)
/// </summary>
[System.Drawing.ToolboxBitmap(typeof(DBLabel), "Resources.DBLabel.bmp")]
public class DataLabel : Label
{
#region Members
private IDataContainer dataContainer;
#endregion
#region Property-bound members
private string _TableName = string.Empty;
private string _ColumnName = string.Empty;
private string _Format = string.Empty;
#endregion
#region Properties
// Tablename
/// <summary>
/// Gets or sets the data table name when this textbox is data bound
/// </summary>
[Category("Daten")]
[Description("Defines the underlying data table when this textbox is data bound")]
public string TableName
{
get { return _TableName; }
set { _TableName = (value == null) ? string.Empty : value; }
}
// ColumnName
/// <summary>
/// Gets or sets the data column when this textbox is data bound
/// </summary>
[Category("Daten")]
[Description("Defines the underlying data column when this textbox is data bound")]
public string ColumnName
{
get { return _ColumnName; }
set
{
_ColumnName = (value == null) ? string.Empty : value;
if ((DesignMode) && (_ColumnName != null)) Text = "<" + _ColumnName + ">";
}
}
// DBValue
/// <summary>
/// Gets or sets the data value of the control
/// </summary>
[Category("Daten")]
[Bindable(true, BindingDirection.OneWay)]
[Description("Defines the underlying data value of the control")]
public object DBValue
{
get { return base.Text; }
set
{
if (value == null || value == DBNull.Value)
base.Text = string.Empty;
else
{
if (_Format != string.Empty)
base.Text = String.Format("{0:" + _Format + "}", value);
else
base.Text = value.ToString();
}
}
}
// Format
/// <summary>
/// Gets or sets the format string of the displayed value
/// </summary>
[Browsable(true)]
[Category("Daten")]
[Description("Gets or sets the format string of the displayed value")]
public string Format
{
get { return _Format; }
set { _Format = value; }
}
#endregion
#region .ctor
public DataLabel() : base()
{
BackColor = Color.Transparent;
if ((DesignMode) && (_ColumnName != null)) Text = "<" + _ColumnName + ">";
}
#endregion
#region Overrides
// CreateHandle
/// <summary>
/// Creates the control´s handle and connects to microkernel in runtime mode
/// </summary>
protected override void CreateHandle()
{
base.CreateHandle();
if (!DesignMode)
{
dataContainer = FindDataContainer();
if (dataContainer != null)
dataContainer.DataBindingChanged += new EventHandler(OnDataBindingChanged);
}
}
#endregion
#region Event handling
void OnDataBindingChanged(object sender, EventArgs e)
{
// Relink data bindings after business context has changed
if ((dataContainer != null) && (_TableName != string.Empty) && (_ColumnName != string.Empty)) try
{
DataBindings.Clear();
DataBindings.Add(dataContainer.GetBinding("DBValue", _TableName, _ColumnName));
}
catch (Exception exc)
{ dataContainer.Throw(exc); }
}
#endregion
#region Methods
private IDataContainer FindDataContainer()
{
Control result = this;
Form ownerForm = FindForm();
while (result != ownerForm)
{
result = result.Parent;
if (result is IDataContainer)
break;
}
return result as IDataContainer;
}
#endregion
}
}
Problem
Das Problem ist, dass durch die Bindung an das eigene Property "DBValue" offensichtlich eine Änderung der Daten stattfindet, denn bereits nach einer Navigation der BindingSource zum nächsten Datensatz liefert DataSet.HasChanges() = true.
Meine Tests:
Wenn ich die Bindung auf das Text Property setze, findet diese Änderung nicht statt:
void OnDataBindingChanged(object sender, EventArgs e)
{
// Relink data bindings after business context has changed
if ((dataContainer != null) && (_TableName != string.Empty) && (_ColumnName != string.Empty)) try
{
DataBindings.Clear();
DataBindings.Add(dataContainer.GetBinding("Text", _TableName, _ColumnName));
}
catch (Exception exc)
{ dataContainer.Throw(exc); }
}
Witzigerweise gilt das auch dann, wenn ich das Property überschreibe und mit dem Code aus meinem eigenen Property "DBValue" fülle:
public override string Text
{
get
{
if ((DesignMode) && (_ColumnName != null))
return "<" + _ColumnName + ">";
else
return base.Text;
}
set
{
if ((DesignMode) && (_ColumnName != null))
base.Text = "<" + _ColumnName + ">";
else
{
if (_Format != string.Empty)
base.Text = String.Format("{0:" + _Format + "}", value);
else
base.Text = value.ToString();
}
}
}
De facto sollte das jetzt identisch sein, außer dass ich ein eigenes Property namens "DBValue" definiert habe, das aber doch exakt das gleiche macht und obendrein die Bindungsrichtung auf "einseitig" festlegt - ich bin ratlos.
**Hintergrund **
Es handelt sich hierbei um nur ein Control unter vielen anderen, die alle eine Schnittstelle namens IDBControl unseres Frameworks implementieren und daher das Property DBValue erforderlich ist.
Bin für jeden Denkanstoß dankbar,
thanx
ron
Setze mal im DBValue - Property im get und im set einen Brakepoint.
Früher (1.1) war es so, dass nach jedem set ein get abläuft... und wenn der an den set übergebene Wert nicht dem Wert aus get entspricht hast Du einen Change.
Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...
hi programmierhans,
die werte stimmen ja aber überein - das problem scheint irgendwas mit persistenz und validierung zu tun zu haben.
gefunden habe ich schon einige internationale threads dazu, aber keiner hat eine befriedigende antwort; folgende funktioniert bei mir z.B. leider nicht:
http://www.devnewsgroups.net/group/microsoft.public.dotnet.framework.windowsforms/topic36268.aspx
Most likely because there is no similar named Changed event, so the property
value always get's persisted at Control.Validating. And a DataRow doesn't
care if it's the same value, if a field is set then it's marked as changed.Add a changed event to your UserControl ...
With a Changed event, it will persist the value at Control.Validating but
only if the Changed event has fired before the Validating event.
Manche gehen in der Tat von einem Bug aus und programmieren eigene Prüfmethoden, um auf Tabellenebene ein HasChanges(string Tablename) zu machen, indem Sie in allen Tabellen des DataSets den aktuellen Datensatz und seine RowVersion angucken - das kann ja wohl nicht sein...
Ich suche mich jetzt schon seit dem ersten Post heute morgen durch diesen sch.... und bin schon am Durchdrehen 🙁
So nun noch alles Zusammengefasst:
Du muss für Dein Beispiel noch folgendes implementieren:
Einen Event mit Namen: DBValueChanged
Ein bool Property mit Namen: DBValueIsNull
Dann sollte es funzen.
Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...
Ich befürchte es handelt sich hier tatsächlich um einen Bug.
Ist schon bisi länger her, da hatte ich massiv Probleme mit HasChanges() und RowState() von DataSets.
Ausführliche Tests und Recherchen haben mich am Ende auch zu dem Schluss kommen lassen, dass es sich um einen Bug handelt.
Vgl: HasChanges() Bug?
Ich hab das Problem dann für mich mit einem häßlichen, dreckigen und unbefriedigenden Workaround gelöst 😉
hi ihr lieben helfer
hab auch nochmal mit den eventnamen herumexperimentiert und tatsächlich: DBValueChanged passt!
Tausend Dank für Eure Unterstützung.
Die Erklärung habe ich allerdings noch nicht so richtig verstanden...?
Gruß
ron
Das mit dem DBValueIsNull musste aber auch noch implementieren... sonst kommt das Problem wieder auf Dich zu wenn Du mit DBNullValues arbeitest.
Mit der Erklärung ist es so eine Sache.... nimm es einfach hin... es ist nun mal so 🙂
Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...
Erklärung ist auch recht einfach.
Per convention muss das ChangedEvent genauso heissen wie das Property,
Oder Du implementierst INotifyPropertyChanged.
Das gibt es seit FW 2.0 und ist natürlich viel einfacher.