I have a List and a Button. When the Lists Count == 0, I would like the button Visibility to = false.
How do I do this using Data Binding?
Thanks in advance,
Added
I have asked this so that I can try to avoid checking the Count on the list in code every time I add or remove an item to or from the list. But if there is no solution then I will continue to do it that way.
Create a DTO (Data Transfer Object) that exposes all your data that you intend to bind to UI elements. Create a property in the DTO (with an appropriate name):
public bool ButtonVisible
{
get { return myListCount != 0; }
}
Add a BindingSource to your form and set it's DataSource to your DTO type.
Click on the Button, goto Properties. Expand the DataBindings node, and click Advanced.
Scroll down the list in the left hand pane, and select Visible. Set it's binding to your property exposed vis the BindingSource..
The General Answer
Write an event handler and register it with your list-control's bindings object
A Specific Example
class MyForm : Form {
protected Button myButton;
BindingSource myBindingSource;
DataGridView dgv;
public MyForm(List someList) {
myBindingSource = new BindingSource();
dgv = new DataGridView();
this.myButton = new Button();
this.Controls.Add(myButton);
this.Controls.Add(dgv);
myBindingSource.DataSource = someList;
dgv.DataSource = myBindingSource;
dgv.DataSource.ListChanged += new ListChangedEventHandler (ListEmptyDisableButton);
}
protected void ListEmptyDisableButton (object sender, ListChangedEventArgs e) {
this.myButton.Visible = this.dgv.RowCount <= 0 ? false : true;
}
}
PS - I'd vote down the favorite answer. A Data Transfer Object (DTO) misses the whole point and functionality of .NET Binding architechture
As the question is currently worded, it doesn't sound like it has anything to do w/ DataBind.
If we have a list -- doesn't matter whether it's populated via code or bound to a data source -- we can set the button's visibility based on the count.
e.g.
List<string> somelist = new List<string>();
somelist.Add("string1");
somelist.Add("string2");
Button1.Visible = somelist.Count > 0 ? true : false;
I think you want to be using the CurrencyManager and the BindingContext of the control.
http://www.akadia.com/services/dotnet_databinding.html#CurrencyManager
Related
I currently have an event set for RowHeaderMouseClick when I click on the header, my textbox is populated with data from the DataGrid. I want to make my textbox populate when I select the row instead of the header. Ideally, I want to hide the header. What is the correct event/property that I need to set to achieve this?
Edit:
Attaching screenshot
You can make like this for any event
//Handle RowChanged.
table.RowChanged += table_RowChanged;
//RowChanged Event.
static void table_RowChanged(object sender, DataRowChangeEventArgs e)
{
Console.WriteLine("... Changed: " + (int)e.Row["Dosage"]);
}
You don't need to do it with events, as you seem to be saying that the textbox gets populated with an item from the row. In such a case you would have:
your datagridview bound to a bindingsource
your bindingsource bound to a datatable or other list that supports binding
your textbox's text property bound to the same bindingsource
Every time the user clicks a row in the grid (or uses the keyboard to move to another row) they are causing the Current property of the bindingsource to update. This in turn changes any of the textboxes that are bound to true same binding source (a textbox only shows the current row item to which it is bound)
For a quick demo of how this works, do these steps (apologies I can't make any screenshots - I'm on a cellphone) - skip any steps you've already done
add a DataSet type file to your project
open it and right click the surface, add a datatable and name it eg Person
right click it and add a couple of columns eg FirstName and LastName
save
switch to the form
open the Dat Sources window (view menu, other windows)
drag the Person node to the form, a datagridview appears as well as some other stuff (bindingsource) - look at the DataSource property of the grid
in the Data sources window again click the drop down button next to Person, change it to details
drag the person node to the form again, this time textboxes appear; take a look at their Text bindings in the (data bindings) section of their properties - they're bound to the same bindingsource as the grid is
run the project, type 4 names into the grid and then select different rows at random using the mouse; the textboxes update to stay current with the grid selection
If this isn't the way you've done things up to now you should consider making it the way; using the DataSet designer to create strongly typed datatables is an easy way to model the data aspects of your program and there is a lot of tooling set up to make life easier when you use them to make a data driven app. If you've been putting data directly into a datagridview it's something you should avoid going forward, and instead separate your concerns in a more mvc style pattern
If you want to remove or make the rowHeader invisible
You can SET an Event DataGridView CellClick and below is the CODE
//Im Assuming that we are getting the row VALUES
try{
int rowIndex = e.RowIndex; //getting the position of ROW in DGV when CLick
int columns = Columns.Count//numebr of Columns
for(int i=0; i< columns;i++)
{
//Here we can populate textBoxes and using FlowLayoutPanel
string values = dataGridView1.Rows[rowIndex].Columns[i].Value.toString();
//Creating new TextBox
TextBox txtBox = new TextBox();
txtBox.Text = values;
txtBox.Size = new Size(100,200);
flowLayoutPanel1.Controls.Add(txtBox);
}
}catch(Exception ee){}
OR If you want to pass the VALUES of the selected ROW you can do like this
textBox.Text = dataGridView1.Rows[rowIndex].Columns[0].Value.toString();//1st col
textBox1.Text = dataGridView1.Rows[rowIndex].Columns[1].Value.toString();//2nd col
First, right-click your dataGrid and select properties
Then change the SelectionMode to FullRowSelect if you like as shown below:
Next, In the datagrid event section double-click on SelectionChanged
and write code like this, you can use other events, although
// Just for example
private void dataGridView1_SelectionChanged(object sender, EventArgs e)
{
try
{
if (dataGridView1.CurrentRow != null && dataGridView1.CurrentRow.Index >= 0)
{
var row = dataGridView1.CurrentRow;
txtCode.Text = (row.Cells["code"].Value != null) ? row.Cells["code"].Value.ToString() : string.Empty;
txtFirstName.Text = (row.Cells["firstName"].Value != null) ? row.Cells["firstName"].Value.ToString() : string.Empty;
txtLastName.Text = (row.Cells["lastName"].Value != null) ? row.Cells["lastName"].Value.ToString() : string.Empty;
}
}
catch { }
}
I set this DataBindingComplete event to my datagridview. I want every datasource that binding to datagridview can be sortable by clicking on column.
void MakeColumnsSortable_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
{
DataGridView dataGridView = sender as DataGridView;
foreach (DataGridViewColumn column in dataGridView.Columns)
column.SortMode = DataGridViewColumnSortMode.Automatic;
}
all of my datasource is List and bindingsource doesnot sort when my list is ended by .ToList
Now . how can i convert datagridview.datasource to Equin.ApplicationFramework.BindingListView and set it again to datasource for make any datagridview sortable?
Proper usage of Equin.ApplicationFramework.BindingListView would be as follows
Upon creation of your form:
Create a BindingListView. It will be filled later with the items that you want to display / sort / filter
Create a BindingSource.
Create a DataGridView.
Add the columns to the properties you want to show
The latter three steps can be done in visual studio designer. If you do that, The code will be in InitializeComponents.
Suppose you want to show / sort / filter elements of MyType Your form will be like:
public class MyForm : Form
{
private BindingListView<MyType> MyItems {get; set;}
public MyForm()
{
InitializeComponent();
this.MyItems = new BindingListView<MyType>(this.components);
// components is created in InitializeComponents
this.MyBindingSource.DataSource = this.MyItems;
this.MyDataGridView.DataSource = this.MyBindingSource;
// assigning the DataPropertyNames of the columns can be done in the designer,
// however doing it the following way helps you to detect errors at compile time
// instead of at run time
this.columnPropertyA = nameof(MyType.PropertyA);
this.columnPropertyB = nameof(MyType.PropertyB);
...
}
You can do without the BindingSource, you can assign the BindingListViewdirectly to the DataSource of the DataGridView. Sorting and Filtering will still work. However the BindingSource will help you to access the currently selected item.
private MyType SelectedItem
{
get {return ((ObjectView<MyType>)this.MyBindingSource.Current)?.Object; }
}
private void DisplayItems (IEnumerable<MyType> itemsToDisplay)
{
this.MyItems.DataSource = itemsToDisplay.ToList();
this.MyItems.Refresh(); // this will update the DataGridView
}
private IEnumerable<MyType> DisplayedItems
{
get {return this.MyItems; }
// BindingListview<T> implements IEnumerable<T>
}
This is all. You don't need to create special functions to sort on mouse clicks. The sorting will be done automatically inclusive deciding on the sort order and displaying the correct sorting glyphs. If you want to sort programmatically:
// sort columnPropertyA in descending order:
this.MyDataGridView.Sort(this.columnPropertyA.ListsortDirection.Descending);
One of the nice things about the BindingListView is the filtering option:
// show only items where PropertyA not null:
this.MyItems.ApplyFilter(myItem => myItem.PropertyA != null);
// remove the filter:
this.MyItems.RemoveFilter();
(I'm not sure if a Refresh() is needed after applying or removing a filter
The context is as below:
A ComboBox is binded to a BindingSource.
That BindingSource is then binded to a DataModel.
That ComboBox has a DataSource of a list of objects. Yet, this list maybe set to
null when under some conditions, and maybe restored under some other conditions.
My problem is on the last step of the below scenario:
At the begining, ComboBox's dataSource is a List of 3 items (eg. item1, item2, item3)
Now comboBox show item1 as the default selected item
User pick item 3 on comboBox
Now comboBox show item3 as the selected item
...some other event happened and cause comboBox's dataSource become null
Now comboBox has no selected item (which selectedIndex is -1)
...some other event happened and cause comboBox's dataSource become the original list
8. Now comboBox's selected item is back to item3, but NOT item1
I would like to understand why will it be so, as i don't want the prvious value being preserved after the dataSource has been null.
Here is how I setup the code, with 1 comboBox binded to the a bindingSource and data-model, and 2 buttons to simulate change of comboBox's dataSource.
BindingModel model = new BindingModel();
public Form1()
{
InitializeComponent();
// BindingSource <--> model
cbxBindingSource.DataSource = typeof(BindingModel);
cbxBindingSource.Add(model);
// ComboxBox <-SelectedValue-> BindingSource <--> model.Result
comboBox.DataBindings.Add(new Binding("SelectedValue", cbxBindingSource, "Result", true, DataSourceUpdateMode.OnPropertyChanged));
}
// Simulate a re-usable list
List<BinderHelper<string>> list = new List<BinderHelper<string>>
{
new BinderHelper<string>("Item1", "1"),
new BinderHelper<string>("Item2", "2"),
new BinderHelper<string>("Item3", "3"),
new BinderHelper<string>("Item4", "4"),
};
private void button2_Click(object sender, EventArgs e)
{
// Simulate triggers that will result in a well-populated data-source
comboBox.DisplayMember = "DisplayMember";
comboBox.ValueMember = "ValueMember";
comboBox.DataSource = list;
}
private void button3_Click(object sender, EventArgs e)
{
// Simulate triggers that will result in a empty data-source
comboBox.DataSource = null;
}
private class BindingModel
{
// Simple model to bind to the selected value of the comboBox
public string Result { get; set; }
}
private class BinderHelper<T>
{
// Use for binding purpose
public string DisplayMember { get; set; }
public T ValueMember { get;set; }
public BinderHelper(string display, T value)
{
DisplayMember = display;
ValueMember = value;
}
}
However, if i create a new list, with new items everytime, then the issue will not be seen.
However, it is not possible in my actual code as the list is always the same instance, just a matter of put it on datasource or not.
Sample as below:
private void button2_Click(object sender, EventArgs e)
{
// Create new list everytime
List<BinderHelper<string>> list = new List<BinderHelper<string>>
{
new BinderHelper<string>("Item1", "1"),
new BinderHelper<string>("Item2", "2"),
new BinderHelper<string>("Item3", "3"),
new BinderHelper<string>("Item4", "4"),
};
// Simulate triggers that will result in a well-populated data-source
comboBox.DisplayMember = "DisplayMember";
comboBox.ValueMember = "ValueMember";
comboBox.DataSource = list;
}
A workaround i did is to check the current dataSource:
If the current dataSource is null, and the new dataSource is not,
Then set SelectedIndex = 0
But i would really want to avoid this manual intervention other than changing the datasource.
Appreciate anyone have a solution on that, thanks in adv!
Updated the working code with suggestions from Ivan:
public Form1()
{
InitializeComponent();
model = new BindingModel();
// BindingSource <--> model
cbxToModelBindingSource.DataSource = typeof (BindingModel);
cbxToModelBindingSource.Add(model);
// New dedicated source to provide the list in combox
// BindingSource <--> comboBox
listToCbxBindingSource = new BindingSource(components);
listToCbxBindingSource.DataSource = typeof (BinderHelper<string>);
comboBox.DataSource = listToCbxBindingSource;
// ComboxBox <--SelectedValue--> BindingSource <--> model.Result
comboBox.DataBindings.Add(new Binding("SelectedValue", cbxToModelBindingSource, "Result", true, DataSourceUpdateMode.OnPropertyChanged));
}
// Simulate triggers that will result in a well-populated data-source
private void button2_Click(object sender, EventArgs e)
{
listToCbxBindingSource.DataSource = list;
}
// Simulate triggers that will result in a empty data-source
private void button3_Click(object sender, EventArgs e)
{
listToCbxBindingSource.DataSource = null;
}
// Additional button to test two-way-bindings
private void button4_Click(object sender, EventArgs e)
{
model.Result = "4";
}
The described behavior is caused by the WinForms data binding infrastructure, in particular the BindingContext class. For each unique data source object it maintains a corresponding BindingManageBase object, which along with other things provides Position and Current property for the data sources - something that they normally don't have - consider IList for example. For list type data source it's an instance of a CurrencyManager class and is created the first time binding manager is requested for a data source object. The important part related to your issue is that there is no mechanism of removing the already created binding managers. Think of it like a dictionary with data source as a key, binding manager as a value created the first time you request a non existing key.
ComboBox control is one of the controls that uses and modifies the Position property when you data bind the list portion of it. When you assign an list to the DataSource property, it synchronizes the selection with the Position, which for newly created binding manger for non empty list is 0. Then when you select an item in the combo box, the Position is updated accordingly. When you set DataSource to null, the combo box items are cleared, but the existing binding manager with the last set position remains inside the BindingContext. The next time you set the same list as data source, it gets the existing binding manager with the last set position, hence the behavior you are getting.
To get rid of this behavior, you should use intermediate BindingSource component. Along with other things, it provides it's own Position, Current property/management and dedicated associated CurrencyManager which allows it do be used a regular data source for controls, at the same time hiding the actual data source from them (and in particular from BindingContext).
Here is how I see the solution. Add another BindingSource component (similar to yours cbxBindingSource), let call it for instance listBindingSource and statically bind the combo box to it:
public Form1()
{
InitializeComponent();
// Make properties of the expected object type available for binding
listBindingSource.DataSource = typeof(BinderHelper<string>);
comboBox.DisplayMember = "DisplayMember";
comboBox.ValueMember = "ValueMember";
comboBox.DataSource = listBindingSource;
// the rest ...
}
Now all you need is to manipulate the DataSource property of the binding source instead of the bound control(s).
private void button2_Click(object sender, EventArgs e)
{
// Simulate triggers that will result in a well-populated data-source
listBindingSource.DataSource = list;
}
private void button3_Click(object sender, EventArgs e)
{
// Simulate triggers that will result in a empty data-source
listBindingSource.DataSource = null;
}
You can simply handle the issue by verifying item.count , so if is equal to zero do nothing
if (Combo1.item.count==0)
{
return;
}
keep above code where ever you would like
Let me know if it helps
So I've found a lot of questions similar to this tho nothing really solve my problem..
I have a combobx that is bounded by a datasource
cmbProduct.DataSource = this.masterDataSet.Product.Where(x => x.Location == getLocation).AsDataView();
cmbProduct.DisplayMember = "Product";
cmbProduct.ValueMember = "Product";
But whenever i update the source, the combobox items does not update automatically. I still need to close then reopen the form.
is there a method to refresh/reload/or update the combobox?
Solution 1
You can use an implementation of IBindingList as DataSource to view changes of data source in the bound list control (complex two-way data binding). The most suitable implementation is System.ComponentModel.BindingList<T>.
Then when you add items to the binding list, or remove item from it you will see changes immediately in the control.
Solution 2
But as a more simple solution with less changes for you, you can reset the databinding of your cmbProduct this way when you need; for example after a change, call RefreshBindings();:
public void RefreshBindings()
{
var list = put your updated list here;
this.cmbProduct.DataSource = null;
this.cmbProduct.DataSource = list;
this.cmbProduct.DisplayMember = "set the display member here";
this.cmbProduct.ValueMember = "set the value member here";
}
You could implement an event that fires whenever the DataSet changes. The event could reset the Datasource and rebind it.
Somewhere in your code:
yourDataController.DataChanged += OnDataChanged;
and the implementation
public void OnDataChanged(object sender, EventArgs e)
{
cmbProduct.Items.Clear();
cmbProduct.DataSource = this.masterDataSet.Product.Where(x => x.Location == getLocation).AsDataView();
cmbProduct.DisplayMember = "Product";
cmbProduct.ValueMember = "Product";
}
Edit: Of course you need to manually implement the event and cause it to fire every time your data changes.
I have a form which has two ComboBoxes. I'd like to use the second (child) ComboBox to display a list of child objects based on user selection of an item from the first.
When the form is instantiated, I databind both controls to private List<Widget>s like so:
private List<ParentWidget> _parentList;
private List<ChildWidget> _childList;
public FormExample()
{
InitializeComponent();
_parentList = GetParentWidgets();
_childList = new List<ChildWidget>();
cmbParent.DisplayMember = "WidgetName";
cmbParent.ValueMember = "ID";
cmbParent.DataSource = _parentList;
cmbChild.DisplayMember = "WidgetName";
cmbChild.ValueMember = "ID";
cmbChild.DataSource = _childList;
}
When the parent selected index is changed, I then populate _childList with the appropriate objects.
The problem is the child ComboBox never shows any of the objects in the collection. It works if I populate the collection with at least one ChildWidget before databinding it, but I'd like it to start empty.
If I understand correctly from another answer, this is failing because the empty list does not contain any properties to bind to. However I am binding to a specific class (Widget) rather than a generic object. Is this not sufficient for the databinding?
When using binding, you should better use the BindingList<> instead, your problem is the List<> does not support notifying changes, so when the data is changed, the control does not know about it and update accordingly. You can use the BindingList<> instead like this:
private BindingList<ParentWidget> _parentList;
private BindingList<ChildWidget> _childList;
That means you have to change the return type of the method GetParentWidgets() to BindingList<ParentWidget>, or you can also use the constructor of a BindingList<> like this:
_parentList = new BindingList<ParentWidget>(GetParentWidgets());
What I would do is listening to the valueChanged event of the parent comboBox and when the event fire I would do the databinding on the child combo.
cmbParent.SelectedValueChanged += OnParentSelectedValueChanged;
private void OnParentSelectedValueChanged(object sender, EventArgs e)
{
this.UpdateChildList(); // Update the data depending on the value in the parent combo
cmbChild.DisplayMember = "WidgetName"; // I guess you can still do this in the constructor
cmbChild.ValueMember = "ID"; // I guess you can still do this in the constructor
cmbChild.DataSource = _childList;
}