Databindings for custom control wont work? - c#

I am having trouble understanding why my databindings do not seem to work with my custom class. I made (hacked) my class extend the Control class to add the databindings functionality but it doesn't actually bind to my custom property.
My code for my custom class is:
public class RadioButtonSet : System.Windows.Forms.Control
{
private Dictionary<System.Windows.Forms.RadioButton, int> buttonList;
private int selectedValue;
public RadioButtonSet()
{
buttonList = new Dictionary<System.Windows.Forms.RadioButton, int>();
}
public void AddButton(System.Windows.Forms.RadioButton button, int buttonValue)
{
if (this.buttonList.ContainsKey(button))
throw new Exception("Button set already contains specified button");
else if (buttonValue <= 0)
throw new Exception("Cannot add specified key to button set");
else if (button == null)
throw new Exception("Parameter button cannot be null");
else
{
button.CheckedChanged += button_CheckedChanged;
this.buttonList.Add(button, buttonValue);
}
}
private void setSelectedButton()
{
this.buttonList.FirstOrDefault(x => x.Value == this.selectedValue).Key.Checked = true;
}
private void button_CheckedChanged(object sender, EventArgs e)
{
System.Windows.Forms.RadioButton btn = sender as System.Windows.Forms.RadioButton;
this.selectedValue = this.buttonList[btn];
}
public int SelectedButton
{
get
{
return selectedValue;
}
set
{
selectedValue = value;
setSelectedButton();
}
}
}
And I try to bind to this class using the following, where rbs_admin is an instance of my custom class:
rbs_admin.DataBindings.Add("SelectedButton", datatable, "admin");
I do not know what information may help so here goes.
I get the information to bind from a datatable which is populated by a data adapter. This custom class is not in it's own file, its part of another static class in my project.
I just dont understand as I created a custom textbox with the same custom property and it binds and works fine.
Any help is much appreciated.

Im talking about something like this:
someListControl.DataSource = datatable;
someListControl.DisplayMember = "someAnotherColumnName"
rbs_admin.DataBindings.Add("SelectedButton", datatable, "admin");
Then, selecting an item from list control will cause your control to update its binding according to the selected item.

Related

Multiple ToggleSwitch instances get same data from the List (ListView) using C# in UWP

This is my first experience with ToggleSwitch. What I am trying to achieve is to show different data from the list using different ToggleSwitches.
I have a ListView with multiple TextBlocks and one ToggleSwitch for each row of data.
Then I populate ListView with data from List. (List is populated using class that forsees
public ToggleSwitch Switch {get; set;}
Here is how I try to get ToggleSwitch data from each row:
private void ToggleSwitch_Toggled(object sender, RoutedEventArgs e)
{
for (int a = 0; a < jointList.Count; a++)
{
jointList[a].Switch = sender as ToggleSwitch;
if (jointList[a].Switch != null)
{
if (jointList[a].Switch.IsOn == true)
{
ToggleTest.Text = jointList[a].ProductId.ToString();
ToggleTest.Visibility = Visibility.Visible;
}
else
{
ToggleTest.Visibility = Visibility.Collapsed;
}
}
}
}
Unfortunately I am getting the same(last added) productId from all of the ToggleSwitches as if they were pointing to same place.
EDIT>
I have rewritten the code as touseef suggested:
private void ToggleSwitch_Toggled(object sender, RoutedEventArgs e)
{
for (int i = 0; i < jointList.Count; i++)
{
if (jointList[i].Value == true)
{
ToggleTest.Text = jointList[i].ProductId.ToString();
// ToggleTest.Text = jointList[a].ProductId.ToString();
ToggleTest.Visibility = Visibility.Visible;
}
else
{
ToggleTest.Visibility = Visibility.Collapsed;
}
}
}
But now nothing shows up.
EDIT:
Here is another attempt to resolve the problem:
private void ToggleSwitch_Toggled(object sender, RoutedEventArgs e)
{
foreach (var record in jointList)
{
if (record.Value == true)
{
ToggleTest.Text = record.ProductId.ToString();
ToggleTest.Visibility = Visibility.Visible;
}
else
{
ToggleTest.Visibility = Visibility.Collapsed;
}
}
}
And now only one ToggleSwitch works, the one that corresponds to the last added record (I was pulling ProductId of the jointList).
None of the other ToggleSwitches work. They don't return any data when using the code above.
Please use DataTemplate to populate a listview and within your datatemplate put a toggleswitch, and x:Bind the IsOn value for your toggleswitch with a bool property within your item's class. and to get the correct values in your c# object behind set two way databinding.
basic databinding : https://learn.microsoft.com/en-us/windows/uwp/data-binding/data-binding-quickstart
binding in depth : https://learn.microsoft.com/en-us/windows/uwp/data-binding/data-binding-in-depth
you can bind with Binding or x:Bind the better way to bind in uwp is x:Bind though, see the links I provided you they will help you a lot :)
Instead of looping the list and getting sender as ToggleSwitch which will obviously give u same instance everytime. you should just loop over the list which you bind to the ListView, and then check your item.IsOn property and get ur item.ProductId and do whateer u want to with ur item object. note that this item came from the List of items which u are binding to the ListView. when u set two way databinding with toggleswitch, your item.IsOn property will automatically change when toggleswitch.IsOn changes, so you don't need to get any instance of toggleswitch in ur code.
INotify
in order to get notified about the propertychange and for two way databinding to work properly, you need to inherit your Product class from following class
public class Observable : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void Set<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
{
if (Equals(storage, value))
{
return;
}
storage = value;
OnPropertyChanged(propertyName);
}
protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
and then in your IsOn property setter method, call onpropertychanged event like this.
public class Product : Observable
{
public int ProductId { get; set; }
private bool isOn;
public bool IsOn
{
get { return isOn; }
set { isOn = value; Set(ref isOn, value, nameof(IsOn)); }
}
}
Toggled Event
private void ToggleSwitch_Toggled(object sender, RoutedEventArgs e)
{
//assuming productList is a List<Product> which was set as ListView.ItemSource
foreach (var product in productList)
{
if (product.IsOn == true)
{
ToggleTest.Text = product.ProductId.ToString();
ToggleTest.Visibility = Visibility.Visible;
}
else
{
ToggleTest.Visibility = Visibility.Collapsed;
}
}
}
if your problem still isn't solved, I will recommend you to put a simplistic app with this problem on the GitHub repo and share the link in your question so people can have a detailed look at it.

How to redraw DataGridView from another form

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.

Exposing DataGridView's columns property in UserControl doesn't work properly

I put a DataGridView in a UserControl and create a public property in my usercontrol that exposes datagridview's columns property.Here is the sample code:
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public DataGridViewColumnCollection MyDataGridColumns
{
get
{
return dataGridView1.Columns;
}
}
}
Then I add UserControl1 in my form and I click on MyDataGridColumns property in property window and add 1 or more columns. The problem happens when I rebuild my solution; All of the columns that I have just added disappear after rebuilding.
Can anyone explain to me why this happens? and how to solve it?
This works for me : I created a specific columns editor as it seems it is impossible to use the default columns editor for any control that does not extend DataGridView.
public partial class UserControl1 : UserControl, IDataGridView
{
public UserControl1()
{
InitializeComponent();
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[Browsable(false)]
public DataGridView DataGridView
{
get { return dataGridView1; }
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[Editor(typeof(ExtendedDataGridViewColumnCollectionEditor), typeof(UITypeEditor))]
[MergableProperty(false)]
public DataGridViewColumnCollection MyDataGridColumns
{
get { return dataGridView1.Columns; }
}
}
public interface IDataGridView
{
DataGridView DataGridView { get; }
}
class ExtendedDataGridViewColumnCollectionEditor : UITypeEditor
{
private Form dataGridViewColumnCollectionDialog;
private ExtendedDataGridViewColumnCollectionEditor() { }
private static Form CreateColumnCollectionDialog(IServiceProvider provider)
{
var assembly = Assembly.Load(typeof(ControlDesigner).Assembly.ToString());
var type = assembly.GetType("System.Windows.Forms.Design.DataGridViewColumnCollectionDialog");
var ctr = type.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0];
return (Form)ctr.Invoke(new object[] { provider });
}
public static void SetLiveDataGridView(Form form, DataGridView grid)
{
var mi = form.GetType().GetMethod("SetLiveDataGridView", BindingFlags.NonPublic | BindingFlags.Instance);
mi.Invoke(form, new object[] { grid });
}
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
if (provider != null && context != null)
{
var service = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
if (service == null || context.Instance == null)
return value;
var host = (IDesignerHost)provider.GetService(typeof(IDesignerHost));
if (host == null)
return value;
if (dataGridViewColumnCollectionDialog == null)
dataGridViewColumnCollectionDialog = CreateColumnCollectionDialog(provider);
//Unfortunately we had to make property which returns inner datagridview
//to access it here because we need to pass DataGridView into SetLiveDataGridView () method
var grid = ((IDataGridView)context.Instance).DataGridView;
//we have to set Site property because it will be accessed inside SetLiveDataGridView () method
//and by default it's usually null, so if we do not set it here, we will get exception inside SetLiveDataGridView ()
var oldSite = grid.Site;
grid.Site = ((UserControl)context.Instance).Site;
//execute SetLiveDataGridView () via reflection
SetLiveDataGridView(dataGridViewColumnCollectionDialog, grid);
using (var transaction = host.CreateTransaction("DataGridViewColumnCollectionTransaction"))
{
if (service.ShowDialog(dataGridViewColumnCollectionDialog) == DialogResult.OK)
transaction.Commit();
else
transaction.Cancel();
}
//we need to set Site property back to the previous value to prevent problems with serializing our control
grid.Site = oldSite;
}
return value;
}
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;
}
}
This is because you didn't specify the type of the column. You should give the type of the column when adding a column (for example DataGridViewTextBoxColumn or DataGridViewCheckBoxColumn). In your Form1.cs do the following:
public Form1()
{
InitializeComponent();
DataGridViewColumn dgViewColumn = new DataGridViewTextBoxColumn();//Or DataGridViewCheckBoxColumn
dgViewColumn.DataPropertyName = "dgViewColumn";
dgViewColumn.HeaderText = #"dgViewColumn";
dgViewColumn.Name = "dgViewColumn";
userControl11.MyDataGridColumns.Add(dgViewColumn);
}
#Bioukh answer works in VS2019 and somewhat works in VS2022. However, the results of embedding the DataGridView control in my UserControl then adding and editing the Columns using the answer does not enable those Columns to migrate to another instance of the UserControl. For example: Copy/Paste the UserControl and all of the embedded DataGridView's columns disappear from the new copy.
To Work Around this issue I maintain my DataGridView instances as native and use a public DataGridView property in my UserControl with the binding and docking performed in the property setter. I then drop my_UserControl on my form, drop my_DataGridView on my form, and then set my_UserControl.DataGridView = my_DataGridView. This work around preserves the native properties and behaviors associated with the DataGridView.
In my_UserControl, I have a Panel named "GridPanel" and a VScrollBar. I then added the following property:
///<summary>
/// Associates a native DataGridView with this UserControl
/// then sets the DataGridView.Parent to the Panel in this UserControl
/// and sets the DataGridView.Dock to Fill the Panel
///</summary>
public DataGridView? ContainedDataGridView
{
get
{
try
{
// if we have a DataGridView in our Panel then return it
if ((this.GridPanel.Controls.Count == 1)
&& (this.GridPanel.Controls[0] is DataGridView view))
{
return view;
}
}
catch (Exception ex)
{
//// TODO Handle "ContainedDataGridView get error"
}
// Return null if there is no DataGridView or there was an error checking for it.
return null;
}
set
{
try
{
// Clear the panel to prevent adding more than one DataGridView
this.GridPanel.Controls.Clear();
if (value is not null)
{
this.GridPanel.Controls.Add(value);
value.Parent = this.GridPanel;
value.Dock = DockStyle.Fill;
}
// else the panel remains cleared
}
catch (Exception ex)
{
//// TODO Handle "ContainedDataGridView set error"
}
}
}
The above snippet is coded as C# 10, .NET 6, Windows Forms App, UserControl and tested in Visual Studio 2022 version 17.0.3

notifying bindingSource.position. Using 2 way binding in winforms to select the current record

I have a bindingSource in winforms as well as a controller class.
I want to be able to set the selected record from within the controller class using 2 way binding.
That is If the form is displaying and I set the SelectedPerson in the controller then the bindingSOurce should make that person the current record.
My controller code is
public class PeopleController : BaseController
{
private SortableBindingList<Person> _blvPersons;
public SortableBindingList<Person> BlvPersons
{
get
{
return this._blvPersons;
}
set
{
this._blvPersons = value;
this.SendChange("BlvPersons");
}
}
private Person _selectedPerson;
public Person SelectedPerson
{
get
{
return this._selectedPerson;
}
set
{
this._selectedPerson = value;
this.SendChange("SelectedPerson");
this.SendChange("BlvPersons");
this.Trace("## SelectedPerson = {0}", value);
}
}
public void InitBindingList
{
using (var repo = new PeopleRepository(new OrganisationContext()))
{
IList<Person> lst = repo.GetList(p => p.Id > 0 && p.Archived == false, x => x.Organisation);
this.BlvPersons = new SortableBindingList<Person>(lst);
} }
}
//ect
}
public class BaseController : INotifyPropertyChanged, IDisposable
{
public event PropertyChangedEventHandler PropertyChanged;
public void SendChange(string propertyName)
{
System.Diagnostics.Debug.WriteLine("PropertyChanged {0} = {1}", propertyName, GetType().GetProperty(propertyName).GetValue(this, null));
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
// etc
I have a bindingSource on my form and set bindingSource.DataSource = controller.BlvPersons
If I Update data values using the controller I will see these changes in the form.
However I cant work out how to set the current record in the controller and see the change in the form.
You can use BindingSource.Find method and set the Position property to the results of the Find method.
The Find method can only be used when the underlying list is an
IBindingList with searching implemented. This method simply refers the
request to the underlying list's IBindingList.Find method.
To implement search on a generic BindingList requires various steps. First, you have to indicate that searching is supported by overriding the SupportsSearchingCore property. Next, you have to implement the IBindingList.Find method, which performs the search.
You can use examples from here or here.
Because I don't want a winforms reference in my controller class, I don't want to share the bindingSource between the form and the controller.
Instead I came up with the idea of having a RecordPosition property in the controller and binding it to a textbox
In my form I have
BindHelper.BindText(this.textRecordPosition,this.controller,"RecordPosition");
private void textRecordPosition_TextChanged(object sender, EventArgs e)
{
this.bindingSource.Position = Convert.ToInt32(textRecordPosition.Text) -1;
}
private void bindingSource_PositionChanged(object sender, EventArgs e)
{
this.controller.RecordPosition = this.bindingSource.Position + 1;
}
In my controller I have
public int RecordPosition
{
get
{
return this._position;
}
set
{
this._position = value;
this.SendChange("RecordPosition");
}
}
In my BindHelper class I have
public static void BindText(TextBox box, object dataSource, string dataMember)
{
var bind = new Binding("Text", dataSource, dataMember, true, DataSourceUpdateMode.OnPropertyChanged);
box.DataBindings.Add(bind);
}

Bind Data to Windows Form TabControl

I'm attempting my first Windows Form project, having been entirely web based previously and experiencing some issues. I want to bind a list of objects to a TabControl and have this create the Tabs and then have a databound value accessible from the click event of each tab.
The Object I'm wanting to bind is
public class TreeNodeItem
{
private NTree<string> node;
public TreeNodeItem(NTree<string> node)
{
this.node = node;
}
public string Value
{
get { return this.node.data; }
}
}
The NTree node represents a node in an object that models data in a tree structure. I want to create a tab for each object in the list with the Value property being bound to the Tab Text property. Other posts mention binding to the ItemsSource property of the control, but Visual Studio is not giving me this property.
Any help will be greatly appreciated.
Cheers
Stewart
Okay, I was unaware of that the binding was a must. Although I have never seen something like this being done in a Windows Forms Application, I've decided to create a class that does this for us.
It uses the ObservableCollection<T> to keep track whether an object / property has been changed inside its list.
public class ObservableList<T> : ObservableCollection<T>
{
public ObservableList() : base()
{
CollectionChanged += new NotifyCollectionChangedEventHandler(nObservableCollection_CollectionChanged);
}
public event PropertyChangedEventHandler OnPropertyChanged;
void nObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (OnPropertyChanged != null)
{
OnPropertyChanged(new object[] { e.OldItems, e.NewItems }, null); // Call method to let it change the tabpages
}
}
}
Now, we have to create a helper class that helps us keeping track:
public class TabControlBind
{
public TabControlBind(TabControl tabControl)
{
// Create a new TabPageCollection and bind it to our tabcontrol
this._tabPages = new TabControl.TabPageCollection(tabControl);
}
// Fields
private ObservableList<TreeNodeItem> _treeNodeItems;
private TabControl.TabPageCollection _tabPages;
// Properties
public ObservableList<TreeNodeItem> TreeNodeItems
{
get { return _treeNodeItems; }
set
{
if (_treeNodeItems != value)
{
_treeNodeItems = value;
_treeNodeItems.OnPropertyChanged += OnPropretyChanged;
OnPropretyChanged(null, null);
}
}
}
public TabControl.TabPageCollection TabPages
{
get
{
return this._tabPages;
}
}
// Events
private void OnPropretyChanged(object sender, PropertyChangedEventArgs e)
{
if (sender == null) // If list got set
{
// Remove existing tabpages
this._tabPages.Clear();
// Loop through all items inside the ObservableList object and add them to the Tabpage
foreach (TreeNodeItem _treeNodeItem in this._treeNodeItems)
{
TabPage tabPage = new TabPage() { Text = _treeNodeItem.Value, Tag = _treeNodeItems };
this._tabPages.Add(tabPage);
}
}
else if (sender is object[]) // If only one (or multiple) objects have been changed
{
// Get OldItems and NewItems
object[] changedItems = (object[])sender;
// Remove OldItems
if (changedItems[0] != null)
{
foreach (dynamic oldItems in (IList)changedItems[0])
{
foreach (TabPage tab in this._tabPages)
{
if (tab.Text == oldItems.Value)
{
this._tabPages.Remove(tab);
break;
}
}
}
}
// Add OldItems
if (changedItems[1] != null)
{
foreach (dynamic newItems in (IList)changedItems[1])
{
TabPage tabPage = new TabPage() { Text = newItems.Value, Tag = newItems };
this._tabPages.Add(tabPage);
}
}
}
}
}
This is a sample on how to use it:
TabControlBind tabControlBinder;
ObservableList<TreeNodeItem> treeNodeItems;
private void btnAdd_Click(object sender, EventArgs e)
{
// This will automatically update the TabControl
treeNodeItems.Add(new TreeNodeItem(new NTree<string>() { data = "Test3" }));
}
private void frmMain_Load(object sender, EventArgs e)
{
// Create a new list object an add items to it
treeNodeItems = new ObservableList<TreeNodeItem>();
treeNodeItems.Add(new TreeNodeItem(new NTree<string>() { data = "Test" }));
treeNodeItems.Add(new TreeNodeItem(new NTree<string>() { data = "Test2" }));
// Create a new instance of the TabControlBind class, set it to our TabControl
tabControlBinder = new TabControlBind(tabControl);
tabControlBinder.TreeNodeItems = treeNodeItems;
}

Categories

Resources