I am hoping to get some pointers on what I am missing in my code.
I have a text box bound to a object property that is an item in the list, and that value doesnt update on the form if I request another item in the list.
To illustrate with example below:
txtGain value is populated after openJSONRequestFileToolStripMenuItem_Click fuction
Once I select something different in cmbSignals combobox, I expect the txtGain value to become updated since SelectedChannel is updated as well, which in turn updates the selectedindex but it doesn't happen.
Basically I want to have my txtGain value updated based on what I select in the cmbSignals. Obviously the binding is there so that I can modify the value in the text box and have it be updated in the property its bound to.
I suspect that I have to somehow force update the bindings but not sure how to do that. Any help would be appreciated.
public partial class MainForm : Form
{
private MyData req;
public MainForm()
{
InitializeComponent();
cmbSignals.DisplayMember = "Name";
cmbSignals.ValueMember = "Value";
}
private void openJSONRequestFileToolStripMenuItem_Click(object sender, EventArgs e)
{
string text = File.ReadAllText("sample.json");
req = new MyData(JsonConvert.DeserializeObject<SerializedRequest>(text));
cmbSignals.DataSource = req.SignalNames;
cmbSignals.SelectedValue = req.SelectedChannel;
SetBindings();
}
private void SetBindings()
{
txtGain.DataBindings.Add(new Binding("Text", req, "Gain"));
}
private void cmbSignals_SelectedValueChanged(object sender, EventArgs e)
{
req.SelectedChannel = Convert.ToInt32(cmbSignals.SelectedValue);
}
}
public class MyData : INotifyPropertyChanged
{
private SerializedRequest Data = new SerializedRequest();
private int selectedIndex = 0;
public int SelectedChannel
{
get
{
return selectedIndex + 1;
}
set
{
this.selectedIndex = value - 1;
}
}
public string Gain
{
get
{
return Data.signals[selectedIndex].gain;
}
set
{
Data.signals[selectedIndex].gain = value;
OnPropertyChanged("Gain");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public List<SignalsCmbItem>SignalNames
{
get
{
List<SignalsCmbItem>channels = new List<SignalsCmbItem>();
for(int i = 0; i<Data.signals.Count;i++)
{
channels.Add(new SignalsCmbItem { Value = i + 1, Name = i+1 + " - " + Data.signals[i].label });
}
return channels;
}
}
}
Pretty annoying "feature", isn't it?.
But no worries, to get around this, add one line of code inside your cmbSignals_SelectedValueChanged(sender, e) method, after you change value of req.SelectedChannel.
txtGain.BindingContext = new BindingContext();
Related
I have a view model that has several properties that are databound to several controls.
When I raise PropertyChanged on one of them, the controls unexpectedly all update. I would expect only the one I am raising the event on to update.
For my form, I have this:
public partial class MainForm : Form
{
AmountCalculatorVM amountCalculatorVM;
public MainForm()
{
InitializeComponent();
}
private void setBindings()
{
textBoxTotalAmount.DataBindings.Add("Text", amountCalculatorVM, "TotalAmount");
textBoxAverage.DataBindings.Add("Text", amountCalculatorVM, "Average",true, DataSourceUpdateMode.Never,null, "#.00");
textBoxCount.DataBindings.Add("Text", amountCalculatorVM, "Count");
listBoxLineAmounts.DataSource = amountCalculatorVM.Amounts;
}
private void MainForm_Load(object sender, EventArgs e)
{
amountCalculatorVM = new AmountCalculatorVM();
setBindings();
}
private void buttonAddAmount_Click(object sender, EventArgs e)
{
if (int.TryParse(textBoxLineAmount.Text.Replace(",", ""), out int amount))
{
amountCalculatorVM.Amounts.Add(amount);
textBoxLineAmount.Text = "";
textBoxLineAmount.Focus();
}
}
private void buttonClear_Click(object sender, EventArgs e)
{
textBoxLineAmount.Text = "";
amountCalculatorVM.Amounts.Clear();
textBoxLineAmount.Focus();
}
}
Then, for my view model, I have this:
class AmountCalculatorVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private readonly AmountList amounts;
public BindingSource Amounts { get; }
public int TotalAmount => amounts.TotalAmount;
public int Count => amounts.Count;
public decimal Average => amounts.Average;
public AmountCalculatorVM()
{
amounts = new AmountList();
Amounts = new BindingSource();
Amounts.DataSource = amounts;
Amounts.ListChanged += Amounts_ListChanged;
}
private void Amounts_ListChanged(object sender, ListChangedEventArgs e)
{
//Any one of these will cause all three textboxes to update in the form
//I would think that with Count and Average commented out, the Count and
//Average textboxes would not update.
OnPropertyChanged("TotalAmount");
//OnPropertyChanged("Count");
//OnPropertyChanged("Average");
//Using any other word will not
//OnPropertyChanged("SomeOtherRandomWord");
}
}
Here is the AmountList class for reference:
class AmountList : List<int>
{
public int TotalAmount
{
get
{
int total = 0;
foreach (int amount in this)
{
total += amount;
}
return total;
}
}
Now, unexpectedly, all three textboxes update if an item is added to the amounts list, which fires ListChanged, and then in turn, the PropertyChanged event.
It doesn't matter which of the three properties I fire PropertyChanged on, but it won't work if I use a different value - it needs to be either TotalAmount, Count, or Average.
I can't understand this behaviour. I would have expected only the text box bound to TotalAmount to be updated, and not the other two, since nothing seems to be notifying them that an update has occurred.
Any ideas?
Why don't you implement the propertychanged like this:
public class Data : INotifyPropertyChanged
{
// boiler-plate
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, string propertyName)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
// props
private string name;
public string Name
{
get { return name; }
set { SetField(ref name, value, "Name"); }
}
}
You can control now, in the setter, which property fires the event:
private string name;
public string Name
{
get { return name; }
set { SetField(ref name, value, "Name"); }
}
you know what I mean?
I have a datagridview for my table Identification with a Status on the last column.
So let's assume I selected 5 rows out of 10 rows on my datagridview.
What I'm trying to do is that when I clicked a button, only the selected rows will be affected and their status will be change.
I have tried this code and other codes and none of them seems to work. I'm new in c#, so is there anybody that could help me?
private void button_Click(object sender, EventArgs e)
{
int count = dataGridView1.SelectedRows.Count;
for (int i = count-1; i >=0; i--)
{
if (i == dataGridView1.SelectedRows.Count)
{
Identification it = new Identification();
it.Status = "ACTIVE";
Repository.Identification_UpdateStatus(it);
}
}
}
You might want to loop through
dataGridView1.SelectedRows to get each DataGridViewRow object
Code:
foreach(DataGridViewRow row in dataGridView1.SelectedRows)
{
// implement your logic here
// update selected rows by making changes to 'row' object
}
The PROPER way to do this would be to use DataBinding. Since you're using domain objects such as 'Identification' it would be a proper fit here.
public partial class Form1 : Form
{
//Your form
public Form1()
{
InitializeComponent();
//Wrap your objects in a binding list before setting it as the
//datasource of your datagrid
BindingList<Identification> ids = new BindingList<Identification>
{
new Identification() { status="NEW" },
new Identification() { status="NEW" },
new Identification() {status="NEW" },
};
dataGridView1.DataSource = ids;
}
private void btnChangeStatus_Click(object sender, EventArgs e)
{ //Where the actual status changing takes place
foreach (DataGridViewRow row in dataGridView1.SelectedRows)
{
var identifaction = row.DataBoundItem as Identification;
identifaction.status = "VERIFIED";
}
}
//Model: Class that carries your data
class Identification: INotifyPropertyChanged
{
private string _status;
public string status
{
get { return _status; }
set
{
_status = value;
NotifyPropertyChanged("status");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
I am making WPFToolkit based Graph where i am trying to update AreaSeries every time i click on a button.
I have implemented INotifyPropertyChanged on my data class. but when i reload the data in the source object id doesn't updates in chart(target object)
the code is as below:
public partial class MainWindow : Window
{
static List<Ready4LOS> Ready4LOS = new List<Data.Ready4LOS>();
public MainWindow()
{
InitializeComponent();
chart1.DataContext = Ready4LOS;
InitChart();
LoadData();
}
private void LoadData()
{
var path = #"zxzxzxz.log";
Ready4LOS.Clear();
List<APISTATDataModel> daa = APISTATDataModel.GetFromFile(path, new string[] { "|" }, "Ready4TOS");
List<APISTATDataModel> lastn = daa.GetRange(daa.Count - 10, 10);
foreach (APISTATDataModel d in lastn)
{
Ready4LOS.Add(new Ready4LOS() { Case = d.Current_Count, Time = d.Current_Time });
}
}
private void InitChart()
{
System.Windows.Data.Binding indi = new System.Windows.Data.Binding("Case");
System.Windows.Data.Binding dep = new System.Windows.Data.Binding("Time");
dep.Mode = System.Windows.Data.BindingMode.OneWay;
indi.Mode = System.Windows.Data.BindingMode.OneWay;
AreaSeries ares = new AreaSeries();
ares.ItemsSource = Ready4LOS;
ares.IndependentValueBinding = dep;
ares.DependentValueBinding = indi;
ares.Title = "Ready4LOS";
DateTimeAxis dta = new DateTimeAxis();
dta.Interval = 10;
dta.IntervalType = DateTimeIntervalType.Minutes;
dta.Title = "Time";
dta.Orientation = AxisOrientation.X;
// dta.Minimum = DateTime.Now.AddMinutes(-90);
// dta.Maximum = DateTime.Now;
LinearAxis yaxis = new LinearAxis();
yaxis.Minimum = 0;
yaxis.Interval = 2;
yaxis.Title = "Case";
yaxis.Orientation = AxisOrientation.Y;
yaxis.ShowGridLines = true;
chart1.Axes.Add(yaxis);
chart1.Axes.Add(dta);
chart1.Series.Add(ares);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
LoadData();
chart1.UpdateLayout();
}
}
}
the data model is here
class Ready4LOS : INotifyPropertyChanged
{
int _case;
DateTime _time;
public int Case
{
get
{
return _case;
}
set
{
_case = value;
NotifyPropertyChanged("Case");
}
}
public DateTime Time
{
get
{
return _time;
}
set
{
_time = value;
NotifyPropertyChanged("Time");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
It loads perfectly when it starts as i've called the LoadData() in the beginning.
The problem is when i click on the refresh button it loads the data in the source object but the target object's data in not updated i.e. chart is not updated it remains the same as of initial data.
Use ObservableCollection<Ready4LOS>, not List<Ready4LOS>. ObservableCollection<> already implements INotifyPropertyChanged and also INotifyCollectionChanged. Your implementation of INotifyPropertyChanged for Ready4LOS may only be necessary if you're going to dynamically change values for Case and Time for existing Ready4LOS already in your collection.
Below is a toy program to illustrate the problem I'm having in my real application. It's a DataGridView with a BindingSource to a list of objects of a class.
The problem is: When I click a CheckBox, the CheckBox visibly changes immediately but the invitation_property_changed() method isn't called until I click on some other cell. I need to get the equivalent of the Checkbox.Checked event as soon as it occurs so I can update another control in the UI. (If I change an OTHERS cell, it's natural for the user to hit Enter which triggers the PropertyChanged event - so this one works naturally for the user.)
Here's a screenshot, fwiw:
Here's my toy code:
namespace dgv_binding_test {
public partial class Form1 : Form {
BindingSource bindingsource_invitations = new BindingSource();
class an_invitation : INotifyPropertyChanged {
static int lastID = 0;
int _id;
string _name;
bool _rsvp;
int _others;
public int id() {
return _id;
}
public string NAME {
get { return _name; }
set {
_name = value;
NotifyPropertyChanged("NAME");
}
}
public bool RSVP {
get { return _rsvp; }
set {
_rsvp = value;
NotifyPropertyChanged("RSVP");
}
}
public int OTHERS {
get { return _others; }
set {
_others = value;
NotifyPropertyChanged("OTHERS");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public an_invitation(string a_name, bool a_rsvp, int others) {
_id = lastID++;
_name = a_name;
_rsvp = a_rsvp;
_others = others;
}
private void NotifyPropertyChanged(String propertyName = "") {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
List<an_invitation> invitations = new List<an_invitation>();
public Form1() {
InitializeComponent();
an_invitation ai = new an_invitation("harry", true, 3);
ai.PropertyChanged += new PropertyChangedEventHandler(invitation_property_changed);
invitations.Add(ai);
ai = new an_invitation("heidi", false, 0);
ai.PropertyChanged += new PropertyChangedEventHandler(invitation_property_changed);
invitations.Add(ai);
ai = new an_invitation("henry", false, 0);
ai.PropertyChanged += new PropertyChangedEventHandler(invitation_property_changed);
invitations.Add(ai);
ai = new an_invitation("hazel", true, 0);
ai.PropertyChanged += new PropertyChangedEventHandler(invitation_property_changed);
invitations.Add(ai);
BindingList<an_invitation> bindingList = new BindingList<an_invitation>(invitations);
bindingsource_invitations = new BindingSource(bindingList, null);
dataGridView1.DataSource = bindingsource_invitations;
dataGridView1.AutoGenerateColumns = true;
}
private void invitation_property_changed(object sender, PropertyChangedEventArgs e) {
Debug.Write("change for id: " + ((an_invitation)sender).id() + " property: " + e.PropertyName + " change: " + ((an_invitation)sender).NAME + " to: ");
if (e.PropertyName == "NAME") {
Debug.WriteLine(((an_invitation)sender).NAME);
} else if (e.PropertyName == "RSVP") {
Debug.WriteLine(((an_invitation)sender).RSVP.ToString());
} else if (e.PropertyName == "OTHERS") {
Debug.WriteLine(((an_invitation)sender).OTHERS.ToString());
}
}
}
}
Thanks for your consideration.
The easiest solution I've discovered for this type of situation is to handle the DataGridView.CellContentClick and DataGridView.CellContentDoubleClick events. You can keep all the current code you have. All you'd need to add is a single handle for both of these events which ends the cell edit when it is a CheckBox cell. Ending the edit will trigger the value change the same as leaving the cell currently does.
this.dataGridView1.CellContentClick += DataGridView1_CellContentClick;
this.dataGridView1.CellContentDoubleClick += DataGridView1_CellContentClick;
private void DataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
if (this.dataGridView1[e.ColumnIndex, e.RowIndex] is DataGridViewCheckBoxCell)
{
this.dataGridView1.EndEdit();
}
}
Prematurely ending a cell's edit can be problematic - say, if it were a TextBox cell - due to validation. But as it's simply True or False, the point is moot in this case.
I have a page that already has a DataContext.
When i change the pivot item, I need to bind another list to another collection.
How to achieve this?
Here is the first DataContext that shows first pivotitem info.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (NavigationContext.QueryString.TryGetValue("id", out _embarqueId))
{
String json = JsonConvert.SerializeObject(_embarqueId);
using (IntrepService service = new IntrepService())
{
String retornojson = service.ObterDetalhesEmbarque(json);
EmbarqueAtual = JsonConvert.DeserializeObject<EmbarqueViewModel>(retornojson);
DataContext = EmbarqueAtual;
}
VerificaConclusao();
}
}
Then I try to load the second collection to the listbox, but doesn't work:
private void Pivot_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!_itemsareloaded && ((PivotItem)pivot.SelectedItem).Header.Equals("itens"))
{
using (IntrepService service = new IntrepService())
{
String json = JsonConvert.SerializeObject(_embarqueId);
var retorno = service.ObterItensEmbarque(json);
ItensDoEmbarque = JsonConvert.DeserializeObject<ObservableCollection<ItemDeEmbarqueViewModel>>(retorno);
lstItens.DataContext = ItensDoEmbarque;
}
}
}
You should have one ViewModel to hold all of your data that you want to bind to. Set this ViewModel as your datacontext.
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<ItemDeEmbarqueViewModel> _itensDoEmbarque;
private EmbarqueViewModel _embarqueAtual;
public ViewModel()
{
ItensDoEmbarque = new ObservableCollection<ItemDeEmbarqueViewModel>();
}
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<ItemDeEmbarqueViewModel> ItensDoEmbarque
{
get { return _itensDoEmbarque; }
set
{
_itensDoEmbarque= value;
OnPropertyChanged("ItensDoEmbarque");
}
}
public EmbarqueViewModel EmbarqueAtual
{
get { return _embarqueAtual; }
set
{
_embarqueAtual = value;
OnPropertyChanged("EmbarqueAtual");
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Within your OnNavigatedTo method set both of the properties and set the DataContext to be this object. You xaml would need to change to bind to the properties of these items instead of {Binding}
You can set the collections that the PivotItem will be bound to ahead of time without worry of rendering delay. PivotItems delay rendering until they are shown.