I am working in Visual Studio running a Windows application.
I am wondering if I can fill a DataGridView from a TextBox, that was a passed value itself?
For example, the user would search for a patient from a dialog form. The patient's name they select would populate a TextBox on my main form. I want that selected patients prior test history to populate a DataGridView on that main form within a tab.
Is this possible, if so how would I accomplish this?
It is possible. I would suggest setting up some sort of data binding. More specifically you will want some class that maintains state and data binds to your controls and possibly your dialog form. I don't know how much you are looking for so this might be going overboard but I would suggest something like this:
public class MainForm : Form
{
public MainForm(StateManager stateManager)
{
_stateManager = stateManager;
//data binding for your text box
txtPatientName.DataBindings.Add(nameof(txtPatientName.Text), stateManager, nameof(stateManager.PatientName));
//data binding for your grid
historyGrid.DataSource = stateManager.History;
}
private void btnShowForm_Click(object sender, EventArgs e)
{
using(var form = new DialogForm())
{
var result = form.ShowDialog();
if(result == DialogResult.Ok)
{
_stateManager.UpdatePatient(form.InputPatientName);
}
}
}
private StateManager _stateManager;
}
//this is the form where you enter the patient name
public class DialogForm : Form
{
//this holds the value where the patient's name is entered on the form
public string InputPatientName { get; set; }
}
//this class maintains your state
public class StateManager : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string PatientName
{
get { return _patientName; }
set
{
_patientName = value;
OnPropertyChanged(nameof(PatientName));
}
}
public BindingList<MedicalHistoryItems> History => _history ?? (_history = new BindingList<MedicalHistoryItems>());
public void UpdatePatient(string patientName)
{
History.Clear();
var historyRetriever = new HistoryRetriever();
History.AddRange(historyRetriever.RetrieveHistory(patientName));
}
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(propertyName);
}
private BindingList<MedicalHistoryItems> _history;
private string _patientName;
}
Related
a super beginner here.
I'm trying to create a shopping cart function in Windows Form C#
I have a form called Shopping_Cart with a datagridview called cartlist.
and I have a seperate form which contains my products called Meat which contains a button to add to cart below each of the products with a textbox BeefQty for quantity.
By the way, the Shopping_Cart and Meat pages are opened through a button from a side panel and are brought to front in the designated space in the window, so my app basically runs in a single window.
What I want to do is, when I click the add button, it will add row into cartlist in the other form without needing to open the Shopping_Cart in a new window.
All the solutions I've searched up thus far includes opening an instance of the other form the button click which I do not want to do.
What I have tried.
In the Meat form
private void AddBeefBtn_Click(object sender, EventArgs e)
{
cartlist.ColumnCount = 3;
cartlist.Columns[0].Name = "Item";
cartlist.Columns[1].Name = "Quantity";
cartlist.Columns[2].Name = "Price";
string[] row = new string[] { "Beef", "BeefQty.text", "10*BeefQty.text" };
cartlist.Rows.Add(row);
}
It gave me error saying that CS0103 : The name 'cartlist' does not exist in the current context which I don't understand since cartlist does exist in the Shopping_Cart form.
Do note that I have absolutely zero experience with C# and Windows Form and that I could care less about OOP concepts right now and I just want the app to function as it should.
Please help.
From your information, the reason that you're receiving CS0103 : The name 'cartlist' does not exist in the current is due to the fact that cartlist exists in Shopping_Cart but not in Meat. They do not share cartlist, hence the error.
The simplest way to resolve this issue is to create a BindingList in a shared class, and bind this list to the cartlist data source.
Given
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Test
{
// Model to use to fill the rows of the DataGridView
public class ItemModel : INotifyPropertyChanged
{
private string item;
private decimal qty, price;
public string Item
{
get => item;
set { item = value; OnPropertyChanged(); }
}
public decimal Quantity
{
get => qty;
set { qty = value; OnPropertyChanged(); }
}
public decimal Price
{
get => price;
set { price = value; OnPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
// This is to notify the grid if any of the properties are updated
private void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
We can create a shared class like this to store the BindingList:
using System.ComponentModel;
namespace Test
{
public static class SharedData
{
public static BindingList<ItemModel> Items { get; set; } = new BindingList<ItemModel>();
}
}
From here, we would setup the form with the DataGridView like so:
using System;
using System.Windows.Forms;
namespace Test
{
public partial class ShoppingCart : Form
{
public ShoppingCart()
{
InitializeComponent();
// Note how we are calling SharedData here
ItemDataGridView.DataSource = SharedData.Items;
}
}
}
And we can add to the BindingList like:
using System;
using System.Windows.Forms;
namespace Test
{
public partial class Meat : Form
{
public Meat()
{
InitializeComponent();
}
private void AddBeefBtn_Click(object sender, EventArgs e)
{
ItemModel model = new ItemModel
{
Item = "Beef",
Quantity = 10M,
Price = 9.95M,
};
SharedData.Items.Add(model);
}
}
}
Output
In my C# windows form I have 2 forms. I would like to display a collection of strings in a label on a form. When I debug I show the 2 elements in my array but they are not showing in the label I am passing it to. When I hover of toString the data is there but how to I pass it to sender so it will display in the label control I have on my form?
In the snippet of code below to data is in toString but how do I get it from there down to sender.ToString????
public AccountReport_cs(Func<string> toString)
{
this.toString = toString;
}
private void AccountReport_cs_Load(object sender, EventArgs e)
{
label1.Text = sender.ToString();
}
This is another piece of the code that will open form2 where the information should be displayed.
private void reportButton2_Start(object sender, EventArgs e)
{
AccountReport_cs accountReport = new AccountReport_cs(allTransactions.ToString);
accountReport.ShowDialog();
}
Here is the last piece of code and this will show how the data gets to EndOfMonth.
public class Transaction
{
public string EndOfMonth { get; set; }
}
public override List<Transaction> closeMonth()
{
var transactions = new List<Transaction>();
var endString = new Transaction();
endString.EndOfMonth = reportString;
transactions.Add(endString);
return transactions;
}
If you need to send information between forms, the best thing you can do is create a property in the target form and assign the value you want to send before displaying the form; thus you will not need to change the default constructor of the form.
// Destiny form
public partial class FormDestiny : Form {
// Property for receive data from other forms, you decide the datatype
public string ExternalData { get; set; }
public FormDestiny() {
InitializeComponent();
// Set external data after InitializeComponent()
this.MyLabel.Text = ExternalData;
}
}
// Source form. Here, prepare all data to send to destiny form
public partial class FormSource : Form {
public FormSource() {
InitializeComponent();
}
private void SenderButton_Click(object sender, EventArgs e) {
// Instance of destiny form
FormDestiny destinyForm = new FormDestiny();
destinyForm.ExternalData = PrepareExternalData("someValueIfNeeded");
destinyForm.ShowDialog();
}
// Your business logic here
private string PrepareExternalData(string myparameters) {
string result = "";
// Some beautiful and complex code...
return result;
}
}
I have a list of items that have a name, price and quantity value.
This list is stored in one form, and this form also had an edit button, so that when a user clicks on a row, they are able to edit this item inside another form that pops up.
I have my code working so that the item changes in the list, however it seems like the DataGridView just isn't updating when the list is changed.
When I edit an item, and add in a new row, it shows the changed values.
Here is my code for my first form:
private void EditButton_Click(object sender, EventArgs e)
{
EditForm editForm = new EditForm();
if (BasketGrid.RowCount > 0)
{
editForm.Show();
}
}
So this juts sets up the button so that it shows the other form.
"BasketGrid" is my DataGridView, that is also given a public initialization at the beginning of my code (Called dgv)
public void EditOkBut_Click(object sender, EventArgs e)
{
this.newName = editNameBox.Text;
decimal price;
int quant;
if (decimal.TryParse(editPriceBox.Text, out price))
{
this.newPrice = price;
}
else
{
MessageBox.Show("Incorrect format for price");
}
if(int.TryParse(editQuantBox.Text, out quant))
{
this.newQuantity = quant;
}
else
{
MessageBox.Show("Incorrect format for quantity");
}
foreach (OrderItem o in basketForm.GetList().ToList())
{
string listName = basketForm.getListName();
if (listName == o.ProductName)
{
o.ProductName = this.newName;
o.ProductPrice = this.newPrice;
o.ProductQuantity = this.newQuantity;
}
}
this.Close();
}
This is my "Edit Button" in my secondary form. This grabs my itemlist from my other form via a method, and compares the product name in of the orderitem in the list, and the listname that the user has selected from the row.
I'd created 'basketForm' as a new object of my other form, so I can access methods and stuff.
I've tried to use basketForm.dgv.Refresh(); but to no avail.
Any help is appreciated.
Cheers,
Daniel
You can use BindingSource and ShowDialog...
Example:
public partial class MainForm : Form
{
private BindingSource bindingSource = new BindingSource();
List<YourData> yourData = new List<YourData>();
public MainForm()
{
InitializeComponent();
bindingSource.DataSource = yourData;
dgv.DataSource = bindingSource;
}
}
Changes will be reflected to your grid like this...
private void EditButton_Click(object sender, EventArgs e)
{
EditForm editForm = new EditForm(yourData);
if (BasketGrid.RowCount > 0)
{
editForm.ShowDialog(this);
bindingSource.ResetBindings(true);
}
}
//Change your Data in EditForm whatever you want
public partial class EditForm : Form
{
List<YourData> yourData;
public EditForm(List<YourData> yourData)
{
InitializeComponent();
this.yourData = yourData;
}
}
You should implement INotifyPropertyChanged interface in the OrderItem class. This will update only one value in DataGridView, instead of updating the entire collection, which may be critical if the collection is very large and its binding may trigger actions, like validation, etc.
class OrderItem : INotifyPropertyChanged
{
private string name;
// other fields : price, quantity
public string Name
{
get { return name; }
set
{
if (value != name)
{
name = value;
NotifyPropertyChanged();
}
}
}
// other properties: Price, Quantity
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Also you have to use BindingList class instead of List. It supports two-way data-binding mechanism.
I am trying to set up a multi-language application, so when the user changes the display language all the texts in all the open windows change automatically.
I am having issues through with binding combo-box control. The binding needs to be done in code-behind as I have dynamic content coming from a database, and sometimes I even have to create additional combo-boxes at runtime.
Also I do not want to keep the translations in the database because I do not want to query the database every time a user is changing the display language.
What I did until now:
in xaml:
<ComboBox x:Name="cmb"/>
and in C#:
public class MyCmbItem
{
public int Index { get; set; }
public string Text { get; set; }
}
private ObservableCollection<MyCmbItem> LoadText()
{
ObservableCollection<MyCmbItem> _result = new ObservableCollection<MyCmbItem>();
foreach (var _item in _list)
{
//the list is coming from a database read
_result.Add(new MyCmbItem { Index = _item.Value, Text = _res_man_global.GetString(_item.KeyText, _culture) });
}
return _result;
}
public ObservableCollection<MyCmbItem> MyTexts
{
get { return LoadText(); }
set {} //I do not have to add/remove items at runtime so for now I leave this empty
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
...
LoadList(); //this adds values in _list
cmb.ItemsSource = MyTexts; //this populates the combo-box
Here I got stuck and I do not know how to determine the combo-box to refresh the displayed texts. The method must achieve that if I have several windows opened each containing a random number of combo-boxes, when I change the current language all the combo-boxes in all the windows will refresh the displayed list, without affecting other values inside (like the selected item). Does anybody know how this can be done?
Many thanks.
For your xaml UI, the INotifyPropertyChanged interface indicates updates of the viewmodel. You can extend your class like this:
public class MyCmbItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string APropertyName)
{
var property_changed = PropertyChanged;
if (property_changed != null)
{
property_changed(this, new PropertyChangedEventArgs(APropertyName));
}
}
private string _Text;
private string _KeyText;
public int Index { get; set; }
public string Text
{
get { return _Text;}
set {
if (_Text != value)
{
_Text = value;
NotifyPropertyChanged("Text");
}
}
}
public MyCmbItem(string key_text, int index)
{
Index = index;
_KeyText = key_text;
RefreshText();
_res_man_global.LanguageChanged += () => RefreshText();
}
public void RefreshText()
{
Text = _res_man_global.GetString(_KeyText, _culture);
}
}
Your view can simply bind to the Text-property as following:
<DataTemplate DataType="{x:Type local:MyCmbItem}">
<TextBlock Text="{Binding Path=Text}"/>
</DataTemplate>
Note: I assumed that your language class is global and has some kind of language-changed notification event.
How to bind a TextBox to an integer? For example, binding unit to textBox1.
public partial class Form1 : Form
{
int unit;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
textBox1.DataBindings.Add("Text", unit, "???");
}
It would need to be a public property of an instance; in this case, the "this" would suffice:
public int Unit {get;set;}
private void Form1_Load(object sender, EventArgs e)
{
textBox1.DataBindings.Add("Text", this, "Unit");
}
For two-way notification, you'll need either UnitChanged or INotifyPropertyChanged:
private int unit;
public event EventHandler UnitChanged; // or via the "Events" list
public int Unit {
get {return unit;}
set {
if(value!=unit) {
unit = value;
EventHandler handler = UnitChanged;
if(handler!=null) handler(this,EventArgs.Empty);
}
}
}
If you don't want it on the public API, you could wrap it in a hidden type somewhere:
class UnitWrapper {
public int Unit {get;set;}
}
private UnitWrapper unit = new UnitWrapper();
private void Form1_Load(object sender, EventArgs e)
{
textBox1.DataBindings.Add("Text", unit, "Unit");
}
For info, the "events list" stuff goes something like:
private static readonly object UnitChangedKey = new object();
public event EventHandler UnitChanged
{
add {Events.AddHandler(UnitChangedKey, value);}
remove {Events.AddHandler(UnitChangedKey, value);}
}
...
EventHandler handler = (EventHandler)Events[UnitChangedKey];
if (handler != null) handler(this, EventArgs.Empty);
You can use a binding source (see comment). The simplest change is:
public partial class Form1 : Form
{
public int Unit { get; set; }
BindingSource form1BindingSource;
private void Form1_Load (...)
{
form1BindingSource.DataSource = this;
textBox1.DataBindings.Add ("Text", form1BindingSource, "Unit");
}
}
However, you'll gain some conceptual clarity if you separate out the data a bit:
public partial class Form1 : Form
{
class MyData {
public int Unit { get; set; }
}
MyData form1Data;
BindingSource form1BindingSource;
private void Form1_Load (...)
{
form1BindingSource.DataSource = form1Data;
textBox1.DataBindings.Add ("Text", form1BindingSource, "Unit");
}
}
HTH. Note access modifiers omitted.
One of the things I like to do is to create "presentation" layer for the form. It is in this layer that I declare the properties that are bound to the controls on the form. In this case, the control is a text box.
In this example I have a form with a textbox to display an IP Address
We now create the binding source through the textbox properties. Select DataBindings->Text. Click the down arrow; select 'Add Project Data Source'.
This starts up that Data Source wizard. Select Object. Hit 'Next'.
Now select the class that has the property that will be bounded to the text box. In this example, I chose PNetworkOptions. Select Finish to end the wizard. The BindingSource will not be created.
The next step is to select the actual property from the bound class. From DataBindings->Text, select the downarrow and select the property name that will be bound to the textbox.
In the class that has your property, INotifyPropertyChanged must implemented for 2-way communication for IP Address field
public class PNetworkOptions : IBaseInterface, INotifyPropertyChanged
{
private string _IPAddress;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public string IPAddress
{
get { return _IPAddress; }
set
{
if (value != null && value != _IPAddress)
{
_IPAddress = value;
NotifyPropertyChanged("IPAddress");
}
}
}
}
In the form constructor, we have to specifically define the binding
Binding IPAddressbinding = mskTxtIPAddress.DataBindings.Add("Text", _NetOptions, "IPAddress",true,DataSourceUpdateMode.OnPropertyChanged);