Hola compañeros tengo una idea para un nuevo componente que he intentado implementar, sin éxito hasta el momento, claro, pensaba que era más fácil, pero de eso nada.
La idea es la siguiente
Crear un panel que vinculamos a un Tdatasource,, con dos propiedades simples TipoDeAccion (xEnabled, xVisible) e AccionEstandar (boolean)
si el state es insert o edit ejecutar según la acción que elijamos y según la accionEstandar
Pudiendo darse cuatro combinaciones
Caso 1
State=Edit o Insert-----------------------State=No Edit o Insert
TipoDeAccion= xEnabled
AccionEstandar =true
Panel Enabled----------------------------Panel No Enablel
Caso 2
State=Edit o Insert-----------------------State=No Edit o Insert
TipoDeAccion= xEnabled
AccionEstandar =False
Panel No Enabled-------------------------Panel Enabled
Caso 3
State=Edit o Insert-----------------------State=No Edit o Insert
TipoDeAccion= xVisible
AccionEstandar =true
Panel Visible------------------------------Panel No visible
y por último Caso 4
State=Edit o Insert-----------------------State=No Edit o Insert
TipoDeAccion= xVisible
AccionEstandar =False
Panel No Visible--------------------------Panel visible
No os pongo el código ya que lo tengo en la empresa y no aquí ahora mismo,
El problema es que no logro detectar los cambios del State del Tdatasourse.
Si podéis ayudarme e incluso poner un ejemplo, más básico, por ejemplo que cambie el color cuando esta en estos modos y normal cuando no, me ayudaría mucho.
Como siempre gracias por vuestra ayuda un saludo, y si termino este componente como siempre lo publicare, como no.

[RESUELTO] Como Crear un Tpanel que detecte los cambios del State del Datasource
Comenzado por
Desart
, feb 02 2011 11:48
7 respuestas en este tema
#1
Escrito 02 febrero 2011 - 11:48
#2
Escrito 02 febrero 2011 - 12:15
Hola.
Para detectar los cambios en el TDatasource tienes que usar el evento OnStateChange del TDatasource.
Por eso yo creo que en la inicialización de tu componente deberías programar ese evento en el TDatasource que tenga asociado.
Saludos.
Para detectar los cambios en el TDatasource tienes que usar el evento OnStateChange del TDatasource.
Por eso yo creo que en la inicialización de tu componente deberías programar ese evento en el TDatasource que tenga asociado.
Saludos.
#3
Escrito 02 febrero 2011 - 01:03
Hola
Lo que estás programando es un control DBAware. Aunque no lo uses para editar, si quieres recibir notificaciones automáticas desde el DataSet / DataSource cada vez que sucede algo, como por ejemplo un cambio en el estado del DataSet, debes utilizar un objeto TDataLink (unit db.pas) o descendiente, que sirven para ligar controles con fuentes de datos.
La clase TDataLink tiene un un método virtual ActiveChanged que es llamado cuando el DataSet vinculado se abre o cierra, y un método EditingChanged que es llamado cuando cambia el estado de dicho DataSet, esto es lo que buscas. Como ambos métodos son virtuales y no hacen nada en la clase TDataLink, puedes o bien crearte una clase descendiente o, lo que te recomiendo, tomar un objeto TFieldDataLink (unit DBCtrls.pas) que ya trae implementado algún código para responder a estos métodos (aunque TFieldDataLink está pensado para ligar con un campo concreto, en tu caso puedes ignorar esta funcionalidad tranquilamente).
Te pongo un esquema de como manejar un objeto de este tipo en tu Panel:
Como ves, ese objeto DataLink hace de nexo entre tu Panel y el DataSource. La propiedad DataSource de tu Panel se toma y se almacena en dicho DataLink (métodos GetDataSource y SetDataSource), y éste ya se encarga de recibir y propagar las notificaciones que tu deseas. Si te fijas, en el constructor asignamos el evento OnEditingChange del DataLink a un método llamado EditingChanged del Panel, ahí dentro programas lo que quieres hacer cuando cambie el estado del DataSet.
(El método Notification se sobreescribe aquí por si se elimina el objeto DataSource ligado, para ponerlo a nil internamente y que no salten access violations.)
Saludos
Lo que estás programando es un control DBAware. Aunque no lo uses para editar, si quieres recibir notificaciones automáticas desde el DataSet / DataSource cada vez que sucede algo, como por ejemplo un cambio en el estado del DataSet, debes utilizar un objeto TDataLink (unit db.pas) o descendiente, que sirven para ligar controles con fuentes de datos.
La clase TDataLink tiene un un método virtual ActiveChanged que es llamado cuando el DataSet vinculado se abre o cierra, y un método EditingChanged que es llamado cuando cambia el estado de dicho DataSet, esto es lo que buscas. Como ambos métodos son virtuales y no hacen nada en la clase TDataLink, puedes o bien crearte una clase descendiente o, lo que te recomiendo, tomar un objeto TFieldDataLink (unit DBCtrls.pas) que ya trae implementado algún código para responder a estos métodos (aunque TFieldDataLink está pensado para ligar con un campo concreto, en tu caso puedes ignorar esta funcionalidad tranquilamente).
Te pongo un esquema de como manejar un objeto de este tipo en tu Panel:
delphi
TMiPanel = class(TPanel) private FDataLink : TFieldDataLink; function GetDataSource: TDataSource; procedure SetDataSource(const Value: TDataSource); protected procedure EditingChanged(Sender: TObject); virtual; procedure Notification(AComponent: TComponent; Operation: TOperation); override; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; published property DataSource: TDataSource read GetDataSource write SetDataSource; end; implementation (...) { TMiPanel } constructor TMiPanel.Create(AOwner: TComponent); begin inherited Create(AOwner); FDataLink := TFieldDataLink.Create; FDataLink.OnEditingChange := EditingChanged; end; procedure TMiPanel.EditingChanged(Sender: TObject); begin // aquí lo que quieras hacer end; destructor TMiPanel.Destroy; begin FreeAndNil(FDataLink); inherited Destroy; end; function TMiPanel.GetDataSource: TDataSource; begin result := FDataLink.DataSource; end; procedure TMiPanel.Notification(AComponent: TComponent; Operation: TOperation); begin inherited Notification(AComponent, Operation); if (Operation = opRemove) AND (FDataLink <> nil) AND (AComponent = DataSource) then DataSource := nil; end; procedure TMiPanel.SetDataSource(const Value: TDataSource); begin if NOT (FDataLink.DataSourceFixed AND (csLoading in ComponentState)) then FDataLink.DataSource := Value; if Assigned(Value) then Value.FreeNotification(Self); end;
Como ves, ese objeto DataLink hace de nexo entre tu Panel y el DataSource. La propiedad DataSource de tu Panel se toma y se almacena en dicho DataLink (métodos GetDataSource y SetDataSource), y éste ya se encarga de recibir y propagar las notificaciones que tu deseas. Si te fijas, en el constructor asignamos el evento OnEditingChange del DataLink a un método llamado EditingChanged del Panel, ahí dentro programas lo que quieres hacer cuando cambie el estado del DataSet.
(El método Notification se sobreescribe aquí por si se elimina el objeto DataSource ligado, para ponerlo a nil internamente y que no salten access violations.)
Saludos
#4
Escrito 03 febrero 2011 - 10:25
Hola andres1569, hice las pruebas y no me ha funcionado, te pongo el código, que copie y pegue, poniendo únicamente la prueba de color
Código
Código
delphi
unit PanelPru; interface uses SysUtils, Classes, Controls, ExtCtrls, Db, Dbctrls, Graphics; type TMiPanel = class(TPanel) private FDataLink : TFieldDataLink; function GetDataSource: TDataSource; procedure SetDataSource(const Value: TDataSource); protected procedure EditingChanged(Sender: TObject); virtual; procedure Notification(AComponent: TComponent; Operation: TOperation); override; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; published property DataSource: TDataSource read GetDataSource write SetDataSource; end; procedure Register; implementation { TMiPanel } constructor TMiPanel.Create(AOwner: TComponent); begin inherited Create(AOwner); FDataLink := TFieldDataLink.Create; FDataLink.OnEditingChange := EditingChanged; end; procedure TMiPanel.EditingChanged(Sender: TObject); begin // aquí lo que quieras hacer if FDataLink.DataSet.State in [dsEdit,dsInsert] then Self.Color:=clGreen else Self.Color:=clBlue; end; destructor TMiPanel.Destroy; begin FreeAndNil(FDataLink); inherited Destroy; end; function TMiPanel.GetDataSource: TDataSource; begin result := FDataLink.DataSource; end; procedure TMiPanel.Notification(AComponent: TComponent; Operation: TOperation); begin inherited Notification(AComponent, Operation); if (Operation = opRemove) AND (FDataLink <> nil) AND (AComponent = DataSource) then DataSource := nil; end; procedure TMiPanel.SetDataSource(const Value: TDataSource); begin if NOT (FDataLink.DataSourceFixed AND (csLoading in ComponentState)) then FDataLink.DataSource := Value; if Assigned(Value) then Value.FreeNotification(Self); end; procedure Register; begin RegisterComponents('Standard', [TMipanel]); end; end.
#5
Escrito 03 febrero 2011 - 11:30
Hola Desart,
En efecto, te recomendé que usaras un TFieldDataLink pero resulta que este objeto está pensado para representar a un campo del DataSet, y si no tiene ninguno vinculado no entra en modo edición, y nunca llega a llamarse al evento OnEditingChange que te comenté.
Al final, mucho más efectivo derivar un objeto propio desde TDataLink y que haga justo lo que queremos, te pongo aquí el código fuente, ahora se usa un TPanelDatalink, por darle algún nombre, aunque bien podría servir para ligar otros controles que sólo necesiten acceder al DataSource sin indicar ningún campo, verás que es muy simple su implementación, solamente sobrescribe el método EditingChanged de TDataLink para que llame al evento que nos interesa:
Esta vez lo he probado y cambia de color según el estado del DataSet
Saludos
NOTA: Le he quitado de la cláusula uses la referencia a Dbctrls, que antes hacía falta para acceder a la clase TFieldDataLink, también he añadido la comprobación "If Assigned(FDataLink.DataSet)" en el método EditingChanged de TMiPanel para evitar que salte algún error de access violation.
En efecto, te recomendé que usaras un TFieldDataLink pero resulta que este objeto está pensado para representar a un campo del DataSet, y si no tiene ninguno vinculado no entra en modo edición, y nunca llega a llamarse al evento OnEditingChange que te comenté.
Al final, mucho más efectivo derivar un objeto propio desde TDataLink y que haga justo lo que queremos, te pongo aquí el código fuente, ahora se usa un TPanelDatalink, por darle algún nombre, aunque bien podría servir para ligar otros controles que sólo necesiten acceder al DataSource sin indicar ningún campo, verás que es muy simple su implementación, solamente sobrescribe el método EditingChanged de TDataLink para que llame al evento que nos interesa:
delphi
unit PanelPru; interface uses SysUtils, Classes, Controls, ExtCtrls, Db, Graphics; type TPanelDataLink = class(TDataLink) private FOnEditingChange: TNotifyEvent; protected procedure EditingChanged; override; public property OnEditingChange: TNotifyEvent read FOnEditingChange write FOnEditingChange; end; TMiPanel = class(TPanel) private FDataLink : TPanelDataLink; function GetDataSource: TDataSource; procedure SetDataSource(const Value: TDataSource); protected procedure EditingChanged(Sender: TObject); virtual; procedure Notification(AComponent: TComponent; Operation: TOperation); override; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; published property DataSource: TDataSource read GetDataSource write SetDataSource; end; procedure Register; implementation { TPanelDataLink } procedure TPanelDataLink.EditingChanged; begin if Assigned(FOnEditingChange) then FOnEditingChange(Self); end; { TMiPanel } constructor TMiPanel.Create(AOwner: TComponent); begin inherited Create(AOwner); FDataLink := TPanelDataLink.Create; FDataLink.OnEditingChange := EditingChanged; end; procedure TMiPanel.EditingChanged(Sender: TObject); begin if Assigned(FDataLink.DataSet) then if FDataLink.DataSet.State in [dsEdit,dsInsert] then Self.Color := clGreen else Self.Color := clBlue; end; destructor TMiPanel.Destroy; begin FDataLink.Free; inherited Destroy; end; function TMiPanel.GetDataSource: TDataSource; begin result := FDataLink.DataSource; end; procedure TMiPanel.Notification(AComponent: TComponent; Operation: TOperation); begin inherited Notification(AComponent, Operation); if (Operation = opRemove) AND (FDataLink <> nil) AND (AComponent = DataSource) then DataSource := nil; end; procedure TMiPanel.SetDataSource(const Value: TDataSource); begin if NOT (FDataLink.DataSourceFixed AND (csLoading in ComponentState)) then FDataLink.DataSource := Value; if Assigned(Value) then Value.FreeNotification(Self); end; procedure Register; begin RegisterComponents('Standard', [TMipanel]); end; end.
Esta vez lo he probado y cambia de color según el estado del DataSet

Saludos
NOTA: Le he quitado de la cláusula uses la referencia a Dbctrls, que antes hacía falta para acceder a la clase TFieldDataLink, también he añadido la comprobación "If Assigned(FDataLink.DataSet)" en el método EditingChanged de TMiPanel para evitar que salte algún error de access violation.
#6
Escrito 03 febrero 2011 - 01:36
Lo prometido es deuda y aquí esta mi componente, muchísimas gracias Andres sin tu ayuda no se si lo hubiese terminado
delphi
//************************************************************************************************************** // NewPanelDb 03/02/2011 // // Panel en el que no tenemos que controlar si esta enble o visible ya que lo detecta del Estado del datasourse // puede controlar en estado norma (cuando esta editando o insertando) esta en enable o visible, por ejemplo, // panel de los datos panel con los botones grabar y cancelar, etc.. O en inverso (cuando esta editando o // insertando) no esta enable o visible, por ejemplo panel con la botonera para movernos por los registros, // busqueda, nuevo, modificar, borrar, salir, imprimir, etc... //-------------------------------------------------------------------------------------------------------------- // Aunque la idea Original es mía J.L.G.T. debo agradecer muchisimo la ayuda de Andres1569 de DelphiAccess sin // el cual no estaría terminado este componente // // Como todos mis componentes, puedes usarlo gratuitamente sin restricciones de ningún tipo //-------------------------------------------------------------------------------------------------------------- // // Propiedades // // Action: TTypeAction; //Tipo de Acción a usar panel enabled o Visible // InverseAction: Boolean; //Si queremos que trabaje al revés, lo normal es State es DsEdit Panel seria // Enabled, si esta opción esta seleccionada seria no enabled // UseColor: Boolean; //Usamos Color En Edit y Color En no Edit // ActiveColor: Tcolor; //Color a usar cuando este en edit o insert // ColorNotActive:Tcolor; //Color a usar cuando no // //*************************************************************************************************************** unit NewPanelDB; interface uses SysUtils, Classes, Controls, ExtCtrls, Db, Graphics, Dialogs; type TTypeAction= (xEnable,xVisible); TPanelDataLink = class(TDataLink) private FOnEditingChange: TNotifyEvent; protected procedure EditingChanged; override; public property OnEditingChange: TNotifyEvent read FOnEditingChange write FOnEditingChange; end; TNewPanelDB = class(TPanel) private FDataLink : TPanelDataLink; FAction: TTypeAction; //Tipo de Acción a usar panel enabled o Visible FInverseAction: Boolean; // Si queremos que trabaje al reves, lo normal es //State es DsEdit Panelñ seria Enabled, si esta //opcion esta seleccionada seria no enabled FUseColor: Boolean; //USamos Color En Edit y Color En no Edit FActiveColor: Tcolor; //Color a usar cuando este en edit o insert FColorNotActive:Tcolor; //Color a usar cuando no function GetDataSource: TDataSource; procedure SetDataSource(const Value: TDataSource); procedure SetAction(const Value: TTypeAction); procedure SetInverseAction(const Value: Boolean); procedure SetUseColor(const Value: Boolean); procedure SetActiveColor(const Value:Tcolor); procedure SetColorNotActive(Const Value:Tcolor); protected procedure EditingChanged(Sender: TObject); virtual; procedure Notification(AComponent: TComponent; Operation: TOperation); override; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; published property DataSource: TDataSource read GetDataSource write SetDataSource; property Action: TTypeAction read FAction write SetAction default xEnable; property InverseAction: Boolean read FInverseAction write SetInverseAction default False; property UseColor: Boolean read FUseColor write SetUseColor default False; property ActiveColor: Tcolor read FActiveColor write SetActiveColor default clMoneyGreen; property ColorNotActive:Tcolor read FColorNotActive write SetColorNotActive default clBtnFace; end; procedure Register; implementation { TPanelDataLink } procedure TPanelDataLink.EditingChanged; begin if Assigned(FOnEditingChange) then FOnEditingChange(Self); end; { TMiPanel } constructor TNewPanelDB.Create(AOwner: TComponent); begin inherited Create(AOwner); FDataLink := TPanelDataLink.Create; FDataLink.OnEditingChange := EditingChanged; FAction:=xEnable; FInverseAction:=False; FUseColor:=False; FActiveColor:=clMoneyGreen; FColorNotActive:=clBtnFace; Color:=clGray; //Esta y la segunda linea es para asegurar los cambios posteriores Color:=clBtnFace; Enabled:=False; end; procedure TNewPanelDB.EditingChanged(Sender: TObject); begin if Assigned(FDataLink.DataSet) then if FDataLink.DataSet.State in [dsEdit,dsInsert] then begin //Caundo insertamos o editamos if FAction=xEnable then //Usando Enabled begin if FInverseAction=False then Self.Enabled:=True else Self.Enabled:=False; end else begin //Usando Visible if FInverseAction=false then Self.Visible:=True else Self.Visible:=False; end; if FUseColor=true then self.Color:=FActiveColor; end else begin //Caundo no insertamos o editamos if FAction=xEnable then //Usando Enabled begin if FInverseAction=False then Self.Enabled:=False else Self.Enabled:=True; end else begin //Usando Visible if FInverseAction=false then Self.Visible:=False else Self.Visible:=True; end; if FUseColor=true then self.Color:=FColorNotActive; end; end; destructor TNewPanelDB.Destroy; begin FDataLink.Free; inherited Destroy; end; function TNewPanelDB.GetDataSource: TDataSource; begin result := FDataLink.DataSource; end; procedure TNewPanelDB.Notification(AComponent: TComponent; Operation: TOperation); begin inherited Notification(AComponent, Operation); if (Operation = opRemove) AND (FDataLink <> nil) AND (AComponent = DataSource) then DataSource := nil; end; procedure TNewPanelDB.SetDataSource(const Value: TDataSource); begin if NOT (FDataLink.DataSourceFixed AND (csLoading in ComponentState)) then FDataLink.DataSource := Value; if Assigned(Value) then Value.FreeNotification(Self); end; procedure TNewPanelDB.SetAction(const Value: TTypeAction); begin if FAction<>Value then FAction:=Value; if FAction=xEnable then begin if FInverseAction=false then Enabled:=false else Enabled:=True; Visible:=True; end else begin if FInverseAction=false then Visible:=false else Visible:=True; Enabled:=true; end; end; procedure TNewPanelDB.SetInverseAction(const Value: Boolean); begin if FInverseAction<>value then FInverseAction:=Value; if FAction=xEnable then begin if FInverseAction=false then Enabled:=false else Enabled:=True; Visible:=true; end else begin if FInverseAction=false then Visible:=false else Visible:=True; Enabled:=true; end; end; procedure TNewPanelDB.SetUseColor(const Value: Boolean); begin if FUseColor<>value then FUseColor:=Value; color:=FColorNotActive; end; procedure TNewPanelDB.SetActiveColor(const Value: TColor); begin if FActiveColor<>value then FActiveColor:=Value; end; procedure TNewPanelDB.SetColorNotActive(const Value: TColor); begin if FColorNotActive<>value then FColorNotActive:=Value; color:=FColorNotActive; end; procedure Register; begin RegisterComponents('Standard', [TNewPanelDB]); end; end.
#7
Escrito 04 febrero 2011 - 11:09
Me alegro de que te haya servido, Jose Luís, y aunque te agradezco que me pongas en los créditos, francamente el componente es idea y realización tuya, si no yo cualquier otro compañero de DelphiAccess te hubiera indicado la mejor manera de vincular con el DataSource.
Para casos como este valdría poner simplemente lo de "DelphiAccess Inside", o bien "Powered by DelphiAccess"
, tal como se comentó en este hilo cuando se eligió el nombre de nuestra mascota
Por otro lado, en los componentes que has ido mostrándonos en el foro veo que siempre prima de forma especial el aspecto visual, ahora me acuerdo de aquél en que hacías un zoom (o le aumentabas el tamaño de letra) al control que estaba activo ...
Saludos
Para casos como este valdría poner simplemente lo de "DelphiAccess Inside", o bien "Powered by DelphiAccess"

Por otro lado, en los componentes que has ido mostrándonos en el foro veo que siempre prima de forma especial el aspecto visual, ahora me acuerdo de aquél en que hacías un zoom (o le aumentabas el tamaño de letra) al control que estaba activo ...

Saludos
#8
Escrito 04 febrero 2011 - 11:37
Gracias Andres, en Cuanto a lo de que suelo optar por el aspecto visual, es cierto suelo intentar que mis programas tengan un aspecto diferente, pero casí siempre basándome en la posible utilidad del componente y el ahorro de código.