C# winApp ค้นหา control ใน control ใน ฟอร์ม ยังไงครับ
pass value by reference ครับ
พื้นฐาน OOP เลย
1. สร้าง class
2. อีก form สร้าง field/property ใน constructor แล้วกำหนดค่า field/property
3. ส่ง control จาก Form หนึ่ง ไปยังอีก form ตอนสร้าง object เพื่ออ้างอิง form ดังกล่าว
กระบวนการนี้ อาจเรียกว่า Dependency Injection (ค้นคำนี้)
แปลเป็นภาษาชาวบ้านว่า form หนึ่ง ขึ้นอยู่กับ(การทำงานของ) อีก form หนึ่ง
https://stackoverflow.com/a/4823535
https://stackoverflow.com/a/60739864
Date :
2022-12-17 10:49:09
By :
009
ตอบความคิดเห็นที่ : 1 เขียนโดย : 009 เมื่อวันที่ 2022-12-17 10:49:09
รายละเอียดของการตอบ ::
คือผมสร้างฟอร์มหลักเป็น คลาสแม่ไว้เปลือยๆ
หลักๆ คือ ต้องการให้ฟอร์มในโปรแกรมมีรูปแบบพื้นฐานเหมือนๆกัน
โดยตอนนี้ ตั้งค่าได้แค่นี้ ครับ
*** ความต้องการเดิมคืออยากได้ datagrid/BindingSource/BindingNavigator มาลงใน คลาสแม่ด้วย แต่ก็ยังหาทางไม่ได้
ก็จะมีโค้ดเบื้องต้นแค่นี้
Code (C#)
public partial class frmSettingDialogBase : frmDialogBase
{
public frmSettingDialogBase()
{
InitializeComponent();
}
private void frmSettingDialogBase_Load(object sender, EventArgs e)
{
foreach (var dataGridView in this.Controls.OfType<DataGridView>())
{
dataGridView.SetDefaultCellStyle();
}
}
}
ซึ่งเมื่อ คลาสลูกเอาไปใช้งาน
ก็จะได้ประมาณนี้ ครับ
Code (C#)
public partial class frmSetting_Bank : frmSettingDialogBase
{
public frmSetting_Bank()
{
InitializeComponent();
}
private void bankBindingNavigatorSaveItem_Click(object sender, EventArgs e)
{
if (bankBindingSource.HasChanges())
{
manEditTextBox.Text = clsUser.UserName;
dateEditDateTimePicker.Value = DateTime.Now;
this.Validate();
this.bankBindingSource.EndEdit();
this.tableAdapterManager.UpdateAll(this.dB_ORGDataSet);
this.bankTableAdapter.Fill(this.dB_ORGDataSet.Bank);
MessageBox.Show("Save data Complete");
}
}
private void frmSetting_Bank_Load(object sender, EventArgs e)
{
// TODO: This line of code loads data into the 'dB_ORGDataSet.Bank' table. You can move, or remove it, as needed.
this.bankTableAdapter.Fill(this.dB_ORGDataSet.Bank);
}
private void bindingNavigatorDeleteItem_Click(object sender, EventArgs e)
{
this.Validate();
this.bankBindingSource.EndEdit();
this.tableAdapterManager.UpdateAll(this.dB_ORGDataSet);
this.bankTableAdapter.Fill(this.dB_ORGDataSet.Bank);
MessageBox.Show("Save data Complete");
}
}
ซึ่งมันแค่ลดโค้ดที่ว่า
Code (C#)
dataGridView.SetDefaultCellStyle();
ลงไป นิดเดียวเอง
โดยจริงๆแล้ว
จะมีโค้ดที่คล้ายๆกัน ใน รุปแบบของแบบฟอร์มตั้งค่าอยู่ คือ
Code (C#)
private void bankBindingNavigatorSaveItem_Click(object sender, EventArgs e)
{
if (bankBindingSource.HasChanges())
{
manEditTextBox.Text = clsUser.UserName;
dateEditDateTimePicker.Value = DateTime.Now;
this.Validate();
this.bankBindingSource.EndEdit();
this.tableAdapterManager.UpdateAll(this.dB_ORGDataSet);
this.bankTableAdapter.Fill(this.dB_ORGDataSet.Bank);
MessageBox.Show("Save data Complete");
}
}
private void frmSetting_Bank_Load(object sender, EventArgs e)
{
// TODO: This line of code loads data into the 'dB_ORGDataSet.Bank' table. You can move, or remove it, as needed.
this.bankTableAdapter.Fill(this.dB_ORGDataSet.Bank);
}
private void bindingNavigatorDeleteItem_Click(object sender, EventArgs e)
{
this.Validate();
this.bankBindingSource.EndEdit();
this.tableAdapterManager.UpdateAll(this.dB_ORGDataSet);
this.bankTableAdapter.Fill(this.dB_ORGDataSet.Bank);
MessageBox.Show("Save data Complete");
}
ซึ่งโค้ดทำนองนี้จะถูกใช้ไปกับ ทุกๆ ฟอร์มตั้งค่า ครับ
เปลี่ยนแค่ชื่อ TableAdapter./BindingSource แค่นั้น เอง
ส่วน manEditTextBox และ dateEditDateTimePicker จะยังเป็นชื่อเดียวกันหมด
ในส่วนของ ฟอร์มที่มี ตารางเดียวหรือ ไม่ได้อยู่ใน panel/groupbox/tabcontrol ก็ใช้ได้ปกติ โดยใช้
Code (C#)
foreach (var dataGridView in this.Controls.OfType<DataGridView>())
{
dataGridView.SetDefaultCellStyle();
}
จะได้ไม่จำเป็นต้องระบุ ชื่อ dataGridView เนื่องจาก ในแต่ละฟอร์มชะชื่อไม่เหมือนกัน ครับ
แต่ก็จะมีปัญหา ในกรณี ที่เรามี dataGridView บน panel/groupbox/tabcontrol จะใช้งาน
Code (C#)
foreach (var dataGridView in this.Controls.OfType<DataGridView>())
{
dataGridView.SetDefaultCellStyle();
}
ไม่ได้
Date :
2022-12-17 16:42:26
By :
lamaka.tor
จริงๆ ผมมีฟอร์ม ที่มีรูปแบบค่อนข้างตายตัวอยู่ ประมาณนี้ ครับ
ซึ่งจะเห็นได้ชัดว่า มันมี control เหมือนๆกัน ไม่กี่ตัว คือ
BindingSource
BindingNavigator
DataGridView
และยังมี control ที่เกี่ยวกับรายละเอียดที่เหมือนๆกัน คือ
IDTextBox
NAME_1TextBox
NAME_2TextBox
remarkTextBox
manEditTextBox
dateEditDateTimePicker
ผมจึงอยากที่จะ สร้างฟอร์มหลัก ไว้เพื่อที่ลดการเขัยนโค้ดลง ครับ
ถ้าทำได้ เราก็จะสามารถ ลดเวลาในการเขียนลงได้เยอะเขึ้น ครับ
Date :
2022-12-17 21:55:43
By :
lamaka.tor
Dependency Injection ตามที่ผมบอกไปครับ
ชื่อมันอาจจะเหมือนเข้าใจยาก
แต่ลองทำการรับส่งข้อมูลระหว่าง form ด้วยการ inject
เมื่อเข้าใจแล้วค่อยต่อยอด และจะเกิดไอเดียในที่สุด
วิธีศึกษาเริ่มจากง่ายไปยาก
เช่น เรียก control แล้วค่อย Nested Controls
...
Date :
2022-12-19 11:04:27
By :
009
น่าจะเขียน tool ขึ้นมาเพื่อสร้าง object เป็นแบบ dynamic
ด้วยการส่ง config เข้าไปใน object แล้ว object นั้น ๆ สร้าง component ตาม config ที่ส่งเข้าไป
ผมไม่ค่อยได้เขียน vs เลยไม่มีตัวอย่าง มีแต่ rad studio ซึ่งเป็น pascal
ตัวอย่าง นี้เป็นการ inherit Class TPanel แล้วแอด component ที่ต้องการเข้าไปใน Panel นั้นๆ
Code (PHP)
unit PLogin;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Edit, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
FMX.Controls.Presentation, FMX.StdCtrls;
type
TLoginCallBack = Procedure(u,p: String) of object;
TpnLogin = class(TPanel)
private
pn: array[0..5] of TPanel;
Cap: TLabel;
User: TEdit;
Pswd: TEdit;
btSave: TButton;
callSave: TLoginCallBack;
Procedure btSaveClick(Sender: TObject);
public
Constructor Create(AOwner: TComponent); override;
Procedure xFree;
property ForSave: TLoginCallBack write callSave;
end;
implementation
uses FMain;
{ TpnLogin }
procedure TpnLogin.btSaveClick(Sender: TObject);
begin
callSave( user.Text, Pswd.Text);
end;
constructor TpnLogin.Create(AOwner: TComponent);
function pnl(pr: TPanel; ta: TAlignLayout; w,h,x,y: Single): TPanel;
begin
result:=TPanel.Create(self);
result.Parent:=pr; result.Align:=ta;
if w>0 then result.Width:=w;
if h>0 then result.Height:=h;
if x>0 then result.Position.X:=x;
if y>0 then result.Position.Y:=y;
result.StyleLookup:='Panel13Style1';
end;
begin
inherited;
Align:=TAlignLayout.Center;
Width:=190; Height:=240; StyleLookup:='Panel13Style1';
//---------------------------------
pn[0]:=pnl(self, TAlignLayout.Top, 0, 40, 0, 0);
Cap:=TLabel.Create(Self);
with Cap do begin
Parent:=pn[0]; Cap.Width:=pn[0].Width*0.95;
TextSettings.Font.size:=16;
TextSettings.Font.Style:=[TFontStyle.fsBold];
TextAlign:=TTextAlign.Center;
Text:='LOGIN'; Align:=TAlignLayout.Center;
end;
//---------------------------------
pn[1]:=pnl(self, TAlignLayout.Bottom, 0, 100, 0, 0);
//---------------------------------
pn[2]:=pnl(self, TAlignLayout.Client, 0, 0, 0, 0);
//---------------------------------
pn[3]:=pnl(pn[2], TAlignLayout.Top, 0, pn[2].Height / 3, 0, 0);
User:=TEdit.Create(self);
with User do begin
Parent:=pn[3]; StyledSettings:=[];
Width:=pn[3].Width * 0.95; Height:=30;
Align:=TAlignLayout.Center; TextAlign:=TTextAlign.Center;
TextSettings.Font.size:=16;
TextSettings.Font.Style:=[TFontStyle.fsBold];
TextPrompt:='Username'; Text:='Kitisak';
end;
//---------------------------------
pn[4]:=pnl(pn[2], TAlignLayout.Client, 0,40, 0, 0);
Pswd:=TEdit.Create(self);
with Pswd do begin
Parent:=pn[4]; StyledSettings:=[]; Password:=true;
Width:=pn[4].Width * 0.95; Height:=30;
Align:=TAlignLayout.Center; TextAlign:=TTextAlign.Center;
TextSettings.Font.size:=16;
TextSettings.Font.Style:=[TFontStyle.fsBold];
TextPrompt:='Password'; Text:='Kitisak001';
end;
//---------------------------------
pn[5]:=pnl(pn[2], TAlignLayout.Bottom, 0, pn[2].Height / 3, 0, 0);
btSave:=TButton.Create(self);
with btSave do begin
Parent:=pn[5]; StyledSettings:=[];
Width:=pn[3].Width * 0.95; Height:=30;
Align:=TAlignLayout.Center; TextAlign:=TTextAlign.Center;
TextSettings.Font.size:=16; Text:='ยืนยัน';
TextSettings.Font.Style:=[TFontStyle.fsBold];
onClick:=btSaveClick;
end;
end;
procedure TpnLogin.xFree;
var i: integer;
begin
Cap.free; User.free; Pswd.free; btSave.free;
for i := 5 downto 0 do begin pn[i].free; end;
end;
end.
Date :
2022-12-19 13:39:29
By :
Chaidhanan
ขอขอบคุณทั้ง 2 ท่านครับ
แต่การที่ เราต้องมานั่งเขียนโค้ดเอง มันเหมือนจะไม่ตอบโจทย์ผมครับ
ยิ่งการทำงานคนเดียวด้วยแล้ว
การทำงานหน้าฟอร์ม จะยิ่งสะดวก สำหรับผมมากครับ
การลากวาง หรือ การมี wizard ให้ใช้ มันยิ่งสะดวกในการเขียนมากขึ้น
แต่พอผมจะลองเปลี่ยนไป .net core หรือ แม้ แต่จะใช้ EF เหมือนมันยิ่งเพิ่มการเขียนโค้ดด้วยตัวเอง มากขึ้น
ยิ่งเราแก้ รายละเอียด ของ database บ่อยเท่าไหร่ ยิ่งเพิ่มข้อผิดพลาดให้โค้ดเรามากขึ้นเท่านั้น
ยิ่งมีโค้ดซ้ำๆ กันมากเท่าไหร่ การแก้ก็จะยิ่งยากขึ้นเป็นเงาตามตัว
เมื่อก่อนผมเคยไล่แก้ ทีละ 10-20 ฟอร์ม ที่คล้ายๆ กัน
อย่างเมื่อก่อน เราอาจจะมีแค่
Code (C#)
this.Validate();
this.bankBindingSource.EndEdit();
this.tableAdapterManager.UpdateAll(this.dB_ORGDataSet);
ต่อมาอยากเพิ่มข้อความแจ้งเตือน
Code (C#)
this.Validate();
this.bankBindingSource.EndEdit();
this.tableAdapterManager.UpdateAll(this.dB_ORGDataSet);
this.bankTableAdapter.Fill(this.dB_ORGDataSet.Bank);
MessageBox.Show("Save data Complete");
แล้วต่อมาก็อยากให้เช็คข้อมูลด้วย
Code (C#)
if (bankBindingSource.HasChanges())
{
manEditTextBox.Text = clsUser.UserName;
dateEditDateTimePicker.Value = DateTime.Now;
this.Validate();
this.bankBindingSource.EndEdit();
this.tableAdapterManager.UpdateAll(this.dB_ORGDataSet);
this.bankTableAdapter.Fill(this.dB_ORGDataSet.Bank);
MessageBox.Show("Save data Complete");
}
ยิ่งแก้มาก โอกาศหลุดพลาด เพราะ เราเองก็อาจจะไม่รู้ว่าโค้ดที่ต้องแก้มันจะไปอยู่ฟอร์มไหนบ้าง
บางที แก้แล้วไม่ถูกใจแก้ใหม่อีกรอบ(อ้าวก็ต้องไปตามแก้ฟอร์มอื่นๆอีก ให้เหมือนๆ กัน)
ผมจึงดึงงานเดิม มาลองทำเป็น ฟอร์มแม่ เพื่อที่หากมีการแก้ไข เราก็จะแก้แค่ ไฟล์เดียว เพื่อให้สืบทอดไปหา คลาสลูกๆ ได้ครับ
ในกรณีที่เราเขียนโค้ดในรับ รับค่าเข้ามาในฟอร์มลูก
ถึงแม้จะทำได้ก็จริง แต่ในกรณีการใช้งาน Binding ต่างๆ ไม่ว่าจะเป็น datagrid หรือ textbox
การ Binding ผ่าน หน้าฟอร์ม ผมมองว่า จะทำงานรวดเร็วกว่า/ลดข้อผิดพลาดจากการพิมพ์มากกว่า เขียนโค้ดเอง ครับ
ซึ่งผมก็เจอปัญหา แบบนี้ ครับ
https://www.thaicreate.com/dotnet/forum/136900.html
ผมเองก็ไม่รู้ ท่านอืนจะทำกันยังไง นะครับ
ไม่ทราบว่า ถ้าเจอ ฟอร์ม ที่คล้ายๆ กัน ซัก 10-20 ฟอร์ม และ ก็ยังมีเคสที่คล้ายๆกันแบบนี้อีกเรื่อยๆ
จะต้องทำยังไง ให้งาน ได้แก้ น้อยที่สุด
Date :
2022-12-19 16:50:36
By :
lamaka.tor
ตอบความคิดเห็นที่ : 6 เขียนโดย : lamaka.tor เมื่อวันที่ 2022-12-19 16:50:36
รายละเอียดของการตอบ ::
จะลากวาง หรือ wizard ก็คือ วิธีการและเป็นส่วนหนึ่งในการพัฒนาโปรแกรม
หรือที่เราเรียกติดปากว่า เขียนโปรแกรม ซึ่งมักมาควบคู่กับการเขียนโค้ด
การแก้ซ้ำจุดเดียว คือ วิธีการพัฒนาแบบ OOP ซึ่งยุคนี้เป็นลักษณะนี้เกือบหมดแล้ว
จุดเด่นของ OOP คือ class/object และการ inherit เรื่องที่ผมแนะนำไป (DI)
เป็นส่วนสำคัญของ OOP ที่โปรแกรมเมอร์หรือผู้ฝักใฝ่โปรแกรมมิ่งควรรู้
ไม่ต่างจาก loop ...ไม่งั้นจะมีคำถามประมาณว่า "ผมใช้ wizard แล้วทำไมต้องแก้ loop?"
หลักการของ DI ก็แค่ ส่ง object เป็น parameter ไปทำงานในอีก object หนึ่ง
ที่นิยมทำ คือ ส่งไปให้ constructor หรือตอนสร้าง object ใหม่นั่นเอง
เช่น
var form1Main = new form1(formMain.panel1.datagridview1 );
txt1.Text = form1Main.dgv.xxx ;
แดง - การ inject form1 class ด้วย datagridview1 จาก formMain
เขียว - สามารถเข้าถึง datagridview1 ของ formMain จาก form1
สามารถทำสลับกันได้ด้วยหลักการเดียวกันนี้
แล้วปัญหาที่ร่ายมา เช่น อันนี้
Code (C#)
foreach (var dataGridView in this.Controls.OfType<DataGridView>())
{
dataGridView.SetDefaultCellStyle();
}
เมื่อเข้าใจหลักการแล้ว ก็จะเข้าถึง nested control ได้
ที่ผมอธิบายสามารถจ่อยอดและประชุกต์ใช้กับ base object
คือแก้ที่ต้นทางที่เดียวเลย ไม่ได้มานั่งแก้ inherit object ทุกรายละเอียด
ที่พิมพ์ตอบน้อยๆ ไม่ใช่ไม่เข้าใจ
แค่อยากให้เข้าใจธรรมชาติของมันด้วย
ทุกอย่างมีข้อจำกัดเสมอ
แต่ทุกข้อจำกัดไม่ใช่ว่าจะถูกทำลายไม่ได้
ยิ่งกรณีนี้ไม่มีข้อจำกัดในเชิง โปรแกรมมิ่ง
เพราะมันถูกออกแบบมาให้เป็น OOP อยู่แล้ว
แต่ถึงกระนั้น ถ้าไม่มีการแตะโค้ด หาใช่โปรแกรมมิ่งไม่
ดังนั้น การพูดว่า สายวาง สายลาก สายโค้ด ไม่ใช่การแบ่งแยก
แต่เป็นข้ออ้างของการปิดกั้นในการพัฒนาตัวเองเท่านั้น
ถ้าไม่สนใจในทางที่ชี้แนะ
ก็ทำวิธี copy/paste และ find/replace เอา
เพราะทุกอย่างที่ทำใน VS มันจะสร้างไฟล์ให้ทุกขั้นตอน
อะไรจะอยู่ไฟล์ไหนก็ไปแกะเอาครับ
สุดท้ายจะเลือกอย่างไหนก็สุดแล้วแต่ความชอบ
Date :
2022-12-19 20:39:33
By :
009
อ้ออออ ขอบคุณมากครับ
ตอนนี้ใช้การ หาคอนโทรล ใน คอนโทรลไปเรื่อยๆ ถ้าพบว่าเป็น DataGridView ก็ตั้งค่าเลยครับ
Code (C#)
void _SetDefaultCellStyle(Control control)
{
foreach (Control ctls in control.Controls)
{
if (ctls is Panel || ctls is GroupBox || ctls is TabPage)
{
Control control1 = (Control)ctls;
foreach (var c in control1.Controls.OfType<DataGridView>())
{
c.SetDefaultCellStyle();
}
}
else
{
_SetDefaultCellStyle(ctls);
}
}
}
Date :
2022-12-20 09:35:45
By :
lamaka.tor
Load balance : Server 02