I'm trying to create pricelist for hotel. I'm having a list of dates, and list of room types in hotel. That lists can contain random number of elements. That is created dynamically, and here is the code:
private void CreateControls() { var colIndex = 0;
var vrsteSoba = _Presenter.VrstaSobeDto.ToArray();
foreach (var bindingItem in vrsteSoba)
{
var lbl = new Label()
{
Width = LABEL_WIDTH,
Height = LABEL_HEIGHT - 5,
Left = 10,
Top = 30 + colIndex * (EDIT_BOX_HEIGHT + SPACE_BETWEEN_CONTROL),
Text = bindingItem
};
_dataPanel.Controls.Add(lbl);
colIndex++;
}
int a = 1;
foreach (var date in _Presenter.CeneTarifa)
{
int y = 0;
var panel = new Panel
{
Height = PANEL_HEIGHT * (vrsteSoba.Length-4),
Width = EDIT_BOX_WIDTH,
Left = a * (EDIT_BOX_WIDTH + SPACE_BETWEEN_CONTROL + 50),
Top = 5
};
_dataPanel.Controls.Add(panel);
var label = new Label
{
Height = EDIT_BOX_HEIGHT,
Location = new Point(0, 10),
Text = date.Datum,
Margin = new Padding(0)
};
panel.Controls.Add(label);
int index = 0;
foreach (var item in date.VrstaSobeCena)
{
var box = new TextBox();
panel.Controls.Add(box);
box.Height = EDIT_BOX_HEIGHT;
box.Width = EDIT_BOX_WIDTH;
box.Location = new Point(0, 30 + y * (EDIT_BOX_HEIGHT + SPACE_BETWEEN_CONTROL));
box.DataBindings.Add(new Binding(nameof(box.Text), date, date.Cena[index].Cena1));
y++;
index++;
}
++a;
}
_dataPanel.AutoScroll = true;
}`
Here is image of that representation.
Now I'm facing a problem of data binding. I need to bind price, two way, for each text box. And I'm stuck.
I have tried to bind it to property name, but then all boxes get same value. If I try to bind it to value via index, I'm getting error
Cannot bind to the property or column 34 on the DataSource. Parameter name: dataMember
Code below is used to fill model that is used in presenter
` private void FillCenePoTarifi() { var sobeArr = VrstaSobeDto.ToArray();
foreach (var datum in Datumi)
{
var dictionary = new Dictionary<string, decimal>();
var cene = new List<Cena>();
foreach (var item in sobeArr)
{
var tarif = _Tarife.Where(x => x.SifTarife == item).FirstOrDefault();
if (tarif != null)
_SastavTarife = HotelierServerLocal.Default.TarifaViewBlo.GetSastaveTarife(tarif.IdTarife);
//proveriti ovu logiku
var cena = _SastavTarife.Where(x => x.Cena1 != 0).Select(c => c.Cena1).FirstOrDefault();
cene.Add(new Cena { Cena1 = cena.ToString()});
dictionary.Add(item, cena);
}
var model = new CenePoTarifi
{
Datum = datum,
VrstaSobeCena = dictionary,
Cena = cene
};
CeneTarifa.Add(model);
}
}`
Finally here are classes that use as model.
` public class CenePoTarifi{
public Dictionary<string, decimal> VrstaSobeCena { get; set; } = new Dictionary<string, decimal>();
public string Datum { get; set; }
private List<Cena> _Cena;
public List<Cena> Cena
{
get => _Cena;
set
{
_Cena = value;
NotifyPropertyChanged("Cena");
}
}
public class Cena :
{
private string _Cena1;
public string Cena1
{
get => _Cena1;
set
{
_Cena = value;
NotifyPropertyChanged("Cena1");
}
}
}`
Does anyone has any suggestions?
Your question is: How to bind dynamically created text box. Here is one tested way for accomplishing that specific task.
First create some textboxes dynamically:
public MainForm()
{
InitializeComponent();
buttonRandom.Click += (sender, e) => generateRandomList();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
List<TextBox> tmp = new List<TextBox>();
for (int column = 1; column < tableLayoutPanel.ColumnCount; column++)
{
for (int row = 1; row < tableLayoutPanel.RowCount; row++)
{
TextBox textBox = new TextBox { Anchor = (AnchorStyles)0xF };
tableLayoutPanel.Controls.Add(textBox, column, row);
tmp.Add(textBox);
textBox.KeyDown += onAnyTextBoxKeyDown;
}
}
_textboxes = tmp.ToArray();
// Generate first dataset
generateRandomList();
}
TextBox[] _textboxes = null;
Then, whenever a new random list is generated, clear any old text and databindings from every TextBox before creating a new data binding for it.
public static Random Rando { get; } = new Random(2);
private void generateRandomList()
{
// Clear ALL the data + bindings for ALL the textboxes.
foreach (var textbox in _textboxes)
{
textbox.Clear();
textbox.DataBindings.Clear();
}
// Generate and create new bindings
int count = Rando.Next(1, 79);
for (int i = 0; i < count; i++)
{
var textbox = _textboxes[i];
VrstaSobeCena vrstaSobeCena =
new VrstaSobeCena{ Sobe = (Sobe)tableLayoutPanel.GetRow(textbox) };
textbox.Tag = vrstaSobeCena;
textbox.DataBindings.Add(
new Binding(
nameof(TextBox.Text),
vrstaSobeCena,
nameof(VrstaSobeCena.Cena),
formattingEnabled: true,
dataSourceUpdateMode: DataSourceUpdateMode.OnPropertyChanged,
null,
"F2"
));
// TO DO
// ADD vrstaSobeCena HERE to the Dictionary<string, decimal> VrstaSobeCena
}
}
The classes shown in your code as binding sources may not bind correctly. One issue I noticed is that the property setters are failing to check whether the value has actually changed before firing the notification. Here's an example of doing that correctly. (For testing purposes I'm showing a Minimal Reproducible Sample "mock" version of a class that implements INotifyPropertyChanged.)
enum Sobe { APP4 = 1, APP5, STUDIO, SUP, APP6, STAND, STDNT, COMSTU, LUXSTU, APP4C, APP4L, APP62, APP6L }
class VrstaSobeCena : INotifyPropertyChanged
{
decimal _price = 100 + (50 * (decimal)Rando.NextDouble());
public decimal Cena
{
get => _price;
set
{
if (!Equals(_price, value))
{
_price = value;
OnPropertyChanged();
}
}
}
Sobe _sobe = 0;
public Sobe Sobe
{
get => _sobe;
set
{
if (!Equals(_sobe, value))
{
_sobe = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Finally, one way to test the two-way binding is to intercept the [Enter] key.
private void onAnyTextBoxKeyDown(object sender, KeyEventArgs e)
{
if ((e.KeyCode == Keys.Enter) && (sender is TextBox textbox))
{
e.SuppressKeyPress = e.Handled = true;
VrstaSobeCena vrstaSobeCena = (VrstaSobeCena)textbox.Tag;
string msg = $"Price for {vrstaSobeCena.Sobe} is {vrstaSobeCena.Cena.ToString("F2")}";
BeginInvoke((MethodInvoker)delegate {MessageBox.Show(msg); });
SelectNextControl(textbox, forward: true, tabStopOnly: true, nested: false, wrap: true);
}
}
Create a List for storing the textbox:
List<TextBox> lstTextbox = new List<TextBox>();
Create a class object that stores the values of "Date" and "room type"
public class RoomTypeDate
{
public string RoomType = "";
public string DateRange = "";
}
Immediately after you created the textbox, assigned the RoomTypeDate info to the tag, add it to lstTextbox.
foreach (var item in date.VrstaSobeCena)
{
var box = new TextBox();
panel.Controls.Add(box);
box.Height = EDIT_BOX_HEIGHT;
box.Width = EDIT_BOX_WIDTH;
box.Location = new Point(0, 30 + y * (EDIT_BOX_HEIGHT + SPACE_BETWEEN_CONTROL));
box.DataBindings.Add(new Binding(nameof(box.Text), date, date.Cena[index].Cena1));
// add the box to the list
lstTextbox.Add(box);
// mark the box with RoomType and DateRange
RoomTypeDate rtd = new RoomTypeDate();
rtd.RoomType = "APP4"; // get the room type
rtd.DateRange = "1.6 - 30.6"; // get date range
box.Tag = rtd;
y++;
index++;
}
Now, to get and set the room price:
public void SetRoomPrice(decimal price, string roomType, string dateRange)
{
foreach (var tb in lstTextBox)
{
var rtd = (RoomTypeDate)tb.Tag;
if(rtd.RoomType == roomType && rtd.DateRange == dateRange)
{
tb.Text = price.ToString();
return;
}
}
}
public decimal GetRoomPrice(string roomType, string dateRange)
{
foreach (var tb in lstTextBox)
{
var rtd = (RoomTypeDate)tb.Tag;
if(rtd.RoomType == roomType && rtd.DateRange == dateRange)
{
return Convert.ToDecimal(rt.Text);
}
}
return 0m;
}
*code untested, might contains bugs
So my problem is as follows: I manages to invoke the filling of the flowLayoutPanel with Buttons, but they refuse to appear in both of the instances of the form. Process being I read in from a db through a SQL query, and fill a List with the results. I then get that list as an args for making them into buttons and store those buttons in an List. I then want to use this List to populate a FlowLayoutPanel in another class which I have made to instances of. Problem arises here where I can only seem to fill one of them at a time (second one that is called).
The class that creates the instances:
public class Oppsett
{
KjokkenSkjerm screenOne;
KjokkenSkjerm screenTwo;
public StatusSkjerm customerScreen;
List<Button> buttonArray = new List<Button>();
System.Timers.Timer refreshTimer;
Thread threadTo;
int counter = 0;
List<Button> sendArrayEn = new List<Button>();
List<Button> sendArrayTo = new List<Button>();
public Oppsett()
{
customerScreen = new StatusSkjerm();
screenOne = new KjokkenSkjerm(customerScreen);
screenTwo = new KjokkenSkjerm(customerScreen);
screenOne.Show();
screenTwo.Show();
customerScreen.Show();
refreshTimer = new System.Timers.Timer(4000);
refreshTimer.Elapsed += OnTimedEvent;
refreshTimer.AutoReset = true;
refreshTimer.Enabled = true;
}
public void addBongAsBtn(List<Bong> bongl)
{
Bong bon = new Bong();
for (int i = 0; i < bongl.Count; i++)
{
Button nextBong = new Button();
bon = bongl[i];
nextBong.Text = bon.bongInfo[0][0].ToString();
nextBong.Size = new Size(186, 186);
nextBong.Tag = bon;
nextBong.Click += selectedBong_ClickEvent;
if (buttonArray.Count == 0)
{
buttonArray.Add(nextBong);
}
else
{
bool exists = false;
for (int j = 0; j < buttonArray.Count(); j++)
{
Bong bb = (Bong)buttonArray[j].Tag;
if ((int)bb.bongInfo[0][0] == (int)bon.bongInfo[0][0])
{
exists = true;
break;
}
}
if (exists == false)
{
buttonArray.Add(nextBong);
}
}
}
screenOne.popArray(buttonArray);
screenTwo.popArray(buttonArray);
}
Then the class KjokkenScreen that contains the popArray method:
public void popArray(List<Button> lb)
{
Thread newThread = new Thread(() =>
{
this.BeginInvoke((Action)delegate ()
{
foreach (Button b in lb)
{
bongPanel.Controls.Add(b);
}
});
});
newThread.Start();
}
I found a solution - create another buttonArray so you have buttonArrayOne and buttonArrayTwo. Populate these with separate objects, so read the same info from the select, but when making new objects create new buttonOne and new buttonTwo. Put these in buttonArrayOne and buttonArrayTwo respectively. Continue like this and then populate each screen with each separate array.
Also use something like this as the populating method:
public void populerArray(List lb)
{
if (bongPanel.InvokeRequired)
{
foreach (Button b in lb)
{
buttonList.Add(b);
Invoke(new MethodInvoker(delegate () { bongPanel.Controls.Add(b); }));
}
}
else
{
foreach (Button b in lb)
{
bongPanel.Controls.Add(b);
}
}
}
Hope this helps someone else!
I’ve created a user control. In the control I have two methods – sending and receiving some data. Those methods also update the data grid located on the user control.
public void RunTX()
{
tx_run = new Thread(new ThreadStart(SendCanFrames));
if (!tx_run.IsAlive)
{
tx_run.IsBackground = true;
tx_run.Start();
}
}
public void RunRX()
{
rx_run = new Thread(new ThreadStart(ReadCanFrames));
if (!rx_run.IsAlive)
{
rx_run.IsBackground = true;
rx_run.Start();
}
}
private void ReadCanFrames()
{
ushort prev_time_stamp = 0;
while (running)
{
if (CanDevice != null)
CanDevice.Read(ref rx_can_msg, 1, ref read_cnt);
if (read_cnt == 1)
{
read_cnt = 0;
dataGridViewCanRx.Rows[0].Cells[0].Value = rx_can_msg[0].Id.ToString("X");
dataGridViewCanRx.Rows[0].Cells[1].Value = rx_can_msg[0].Size.ToString();
dataGridViewCanRx.Rows[0].Cells[2].Value = BytesToString(rx_can_msg[0].Data);
dataGridViewCanRx.Rows[0].Cells[3].Value = (rx_can_msg[0].TimeStamp - prev_time_stamp).ToString();
prev_time_stamp = rx_can_msg[0].TimeStamp;
}
prev_time_stamp = rx_can_msg[0].TimeStamp;
}
}
private void SendCanFrames()
{
if (tx_can_msg.Length == 0) return;
VSCAN_MSG[] l_msgs = new VSCAN_MSG[2];
while (running)
{
for (int i = 0; i < tx_can_msg.Length; i++)
{
if (can_messages[i].CountRun < can_messages[i].CountMax)
{
can_messages[i].TimeStamp1 = DateTime.Now;
interval = can_messages[i].TimeStamp1 - can_messages[i].TimeStamp2;
if (interval.TotalMilliseconds >= can_messages[i].Period)
{
can_messages[i].TimeStamp2 = DateTime.Now;
l_msgs[0] = tx_can_msg[i];
//send CAN frame
CanDevice.Write(l_msgs, 1, ref written_cnt);
// send immediately
CanDevice.Flush();
can_messages[i].CountRun++;
dataGridViewCanTx.Rows[i].Cells[4].Value = can_messages[i].CountRun.ToString();
}
}
}
}
}
I put four user control instances on the main form and start all four controls.
private void buttonStartAll_Click(object sender, EventArgs e)
{
int can_channel;
for (can_channel = 0; can_channel < 4; can_channel++)
{
if (started[can_channel] == false)
{
if (connected[can_channel] == true)
{
switch (can_channel)
{
case 0:
mainform.userControlCan1.RunTX();
mainform.userControlCan1.RunRX();
started[can_channel] = true;
break;
case 1:
mainform.userControlCan2.RunTX();
mainform.userControlCan2.RunRX();
started[can_channel] = true;
break;
case 2:
mainform.userControlCan3.RunTX();
mainform.userControlCan3.RunRX();
started[can_channel] = true;
break;
case 3:
mainform.userControlCan4.RunTX();
mainform.userControlCan4.RunRX();
started[can_channel] = true;
break;
}
}
}
}
}
All is running, however the GUI freezing and data grids updated by jumps. Why?
I recomend you to look at two key points: Invoke and Application.DoEvents.
First one will help you to update your GUI in main thread instead of child threads.
Have a look at : https://learn.microsoft.com/en-us/dotnet/api/system.reflection.methodbase.invoke?view=netframework-4.8
Application.DoEvents() method tells the system to do other waiting jobs without blocking in some loops. Otherwise updating GUI jobs are postponed and GUI is blocked. Here is the information about this method: https://learn.microsoft.com/tr-tr/dotnet/api/system.windows.forms.application.doevents?view=netframework-4.8
private delegate void dlgUpdateRows(object[] rx_can_msg, int tID);
// Write actual type of rx_can_msg instead of object[] in method signature , second parameter should be your thread id if needed
private void UpdateRows(object[] rx_can_msg, int tID =0)
{
try
{
if (this.InvokeRequired)
{
object[] obj = new object[2];
obj[0] = rx_can_msg;
obj[1] = Thread.CurrentThread.ManagedThreadId;
this.Invoke(new dlgUpdateRows(UpdateRows), obj);
}
else
{
//Here update your datagrid using rx_can_msg
}
//This row is important to avoid blocking
Application.DoEvents();
}
catch (Exception ex)
{
//Do error handling
}
}
I'm trying to create a column programmatically and set it as checkbox.
I'm doing it on Expanded event.
The snippet of my code is
if (qtdColumns < qtdPermissions)
{
for (int i = qtdColumns; i < qtdPermissions; i++)
{
OLVColumn aNewColumn = new OLVColumn();
aNewColumn.Text = string.Empty;
aNewColumn.AspectGetter += delegate (object x)
{
if (((Model)x).permissions.Count() > 0)
{
aNewColumn.CheckBoxes = true;
return permission.Label;
}
else
{
aNewColumn.CheckBoxes = false;
return "";
}
};
this.MyTreeListView.AllColumns.Add(aNewColumn);
qtdColumns++;
}
}
But the only result I get is like this:
Boys and Girls,
I got this method (task) that gets executed when I select a node in a treeview. It retreives data from a database and puts in a ReportControl (Codejock).
What I need is to prevent that this method (task) gets executed again while it is still running.
I've been experimenting with booleans set to false when starts and set to true if finishes but that doesn't work for some reason.......
here is the code:
the event where the method gets executed:
private void tvObjects_AfterSelect(object sender, TreeViewEventArgs e)
{
try
{
tvObjects.PreseveTreeState = true;
tvObjects.SaveTreeState();
tvObjects.SelectedNode.BackColor = Color.FromArgb(191, 210, 234);
AllowPreview = false;
WordPreviewer.UnloadPreviewHandler();
viewer1.Image = null;
rcDocumenten.ClearContent();
rcEmail.ClearContent();
var n = e.Node as ExtendedTreeNode;
tvObjects.CurrentNode = e.Node;
SelectedObjectNode = n;
WordPreviewer.FileName = null;
if (n != null)
{
Document.SetDossierNummer(n.DossierNr);
}
var selNode = e.Node as ExtendedTreeNode;
if (selNode != null && selNode.DossierNode)
{
if (selNode.IsFolder)
{
DossierNr = Convert.ToInt32(selNode.DossierNr);
SelectedObjectNode = selNode;
var col = new col();
col.CreateCurrentDossierDocumentsList(Convert.ToInt32(selNode.DossierNr.ToString()),
selNode.Tag.ToString());
col.CreateCurrentEmailList(selNode.DossierNr, Convert.ToInt32(selNode.Tag.ToString()));
var t =
new Thread(
() =>
rcDocumenten_Populate(Convert.ToInt32(selNode.DossierNr.ToString()),
selNode.Tag.ToString()));
t.Start();
var t2 = new Thread(
() => rcEmail_Populate(selNode.DossierNr, Convert.ToInt32(selNode.Tag.ToString())));
t2.Start();
tcDocumenten.SelectedTab = selNode.Text.Contains("Email") ? tpEmail : tpDocumenten;
}
else
{
tpDocumenten.Text = #"Documenten (0)";
tpEmail.Text = #"Emails (0)";
SelectedBestandId = -1;
SelectedBestandsNaam = string.Empty;
SelectedEmailId = -1;
SelectedEmailOnderwerp = string.Empty;
}
}
else if (selNode != null && selNode.PersonalNode)
{
if (!selNode.IsMedewerker)
{
var t =
new Thread(
() => rcDocumenten_PersoonlijkeMappenPopulate(Convert.ToInt32(selNode.Tag.ToString())));
t.Start();
}
}
}
catch (InvalidOperationException iex)
{
MessageBox.Show(iex.ToString());
}
catch (Exception ex)
{
var dmsEx = new DmsException("Fout tijdens het uitvoeren event AfterSelect tvObjects ", "VDocumenten (tvObjects Event: AfterSelect)", ex);
ExceptionLogger.LogError(dmsEx);
}
}
the method that should not run twice:
public void rcDocumenten_PersoonlijkeMappenPopulate(int personalFolderId)
{
try
{
AllowPreview = false;
var oc = new col();
rcDocumenten.FocusedRow = null;
oc.CreateCurrentPersoonlijkeDocumentsList(personalFolderId);
UpdateUI(false);
if (rcDocumenten.InvokeRequired)
{
rcDocumenten.Invoke((MethodInvoker)delegate
{
rcDocumenten.Records.DeleteAll();
rcDocumenten.Redraw();
_gegevensLaden = new GegevensLaden(this);
_gegevensLaden.Show();
//Documenten uit Database ophalen
_gegevensLaden.progressbar.Maximum = col.ListPersoonlijkeDocuments.Count;
foreach (var document in col.ListPersoonlijkeDocuments)
{
var versie = Convert.ToDecimal(document.Versie.ToString());
if (document.OriBestandId == 0)
{
//Record toevoegen
rcDocumenten_Persoonlijk_AddRecord(document.BestandId, document.BestandsNaam, versie,
document.DatumToevoeg, document.DatumUitcheck, document.UitgechecktDoor, document.Eigenaar,
document.DocumentType, document.DocumentProgres);
}
_gegevensLaden.progressbar.Value = _gegevensLaden.progressbar.Value + 1;
_gegevensLaden.progressbar.Update();
}
var aantalRecords = 0;
for (var i = 0; i < rcDocumenten.Records.Count; i++)
{
aantalRecords++;
for (var j = 0; j < rcDocumenten.Records[i].Childs.Count; j++)
{
aantalRecords++;
}
}
tpDocumenten.Text = #"Documenten (" + aantalRecords + #")";
rcDocumenten.Populate();
Invoke(new UpdateUIDelegate(UpdateUI), new object[] { true });
});
}
//"dd-MM-yyyy HH:mm:ss"
AllowPreview = true;
}
catch (Exception ex)
{
var dmsEx = new DmsException("Fout bij de populatie van Report Control", "VDocumenten (rcDocumenten_Persoonlijk_Populate)", ex);
ExceptionLogger.LogError(dmsEx);
}
}
You can check if the thread/task has completed. Change the thread creation to use a Task
_t =
Task.Factory.StartNew(
() =>
rcDocumenten_Populate(Convert.ToInt32(selNode.DossierNr.ToString()),
selNode.Tag.ToString()));
Then you can keep the Task around in the class scope. As you see above, I called it _t.
private Task _t; // documenten vullen achtergrond thread
Now, instead of blindly starting the Task, check if the Task should be started.
if (_t == null || _t.IsCompleted) {
That would solve your current issue.