Bind Data to Windows Form TabControl - c#

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;
}

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.

Databindings for custom control wont work?

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.

WPF How to raise an Individual property change from DataGrid bounded to a ObservableCollection

As the question ask, how is this done ?
From a DataGrid I can get the SelectedRow of that collection that has been bounded to its ItemSource, but I really need a setter and getter for an individual property that belongs inside the ObservableCollection.
For example, I need to catch when a user checks a bool property inside a datagrid, so then the setter would be set to "false/true". So something like
//But the Archive property is in the DataContext of the row item...
//so this wouldnt work, I think..
private bool m_Archived = false;
public bool Archived
{
get { return m_Archived; }
set
{
m_Archived = value;
OnPropertyChanged("Archived");
}
}
But remember this property is part of the ObservableCollection (DataContext)
Cheers
You need to register to each collection item property changed, then to control when the desired property has changed, then change the archived property. Check this sample code:
private ObservableCollection<TClass> _SomeObservableCollection;
public ObservableCollection<TClass> SomeObservableCollection
{
get { return _SomeObservableCollection ?? (_SomeObservableCollection = SomeObservableCollectionItems()); }
}
private ObservableCollection<TClass> SomeObservableCollectionItems()
{
var resultCollection = new ObservableCollection<TClass>();
foreach (var item in SomeModelCollection)
{
var newPoint = new TClass(item) {IsLocated = true};
newPoint.PropertyChanged += OnItemPropertyChanged;
resultCollection.Add(newPoint);
}
resultCollection.CollectionChanged += OnSomeObservableCollectionCollectionChanged;
return resultCollection;
}
private void OnSomeObservableCollectionCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (TClass TClass in e.NewItems)
{
TClass.PropertyChanged += OnItemPropertyChanged;
}
}
if (e.OldItems != null)
{
foreach (TClass TClass in e.OldItems)
{
TClass.PropertyChanged -= OnItemPropertyChanged;
}
}
if (!Patient.HasChanges)
Patient.HasChanges = true;
}
private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName != "ItemArchivedProperty") return;
// set Archived = true or set Archived = false
}
This is just an example, but it should works. Hope it helps.

ObservableCollection not updating the control

I am having a Telerik TransitionControl which displays advertisements to to end user. the logic is written in such a way that the ad images will be downloaded asynchronously in the behind. the control will display images as it is available. I am using ObservableCollection to hold the advertisement images.New image information is added to this ObservableCollection when a image is successfully downloaded. However, the Telerik TransitionControl is not getting updated with the new images.
I believe the ObservableCollection does not need the OnNotifyPropertyChanged to be called as it will be called internally
Code is given below
//Inside the AdvertUserControl.xaml.cs
ViewModel vm = new ViewModel();
DataContext = vm;
this.radControl.SetValue(AdRotatorExtensions.AdRotatorExtensions.ItemsSourceProperty, vm.SquareAdsVertical);
//Inside the ViewModel.cs
public ReadOnlyObservableCollection<Advert> SquareAdsVertical
{
get
{
if (AdsManager.VerticalAds == null)
{
return null;
}
return new ReadOnlyObservableCollection<Advert>(AdsManager.VerticalAds);
}
}
// Inside DownloadManager.cs
private static ObservableCollection<Advert> adsToShowVertical = new ObservableCollection<Advert>();
public static ObservableCollection<Advert> VerticalAds
{
get { if (adsToShowVertical != null) return adsToShowVertical;
return null;
}
}
public static void OnDownloadComplete(Object sender, AsyncCompletedEventArgs e)
{
try
{
if(!e.Cancelled)
{
if (e.Error == null)
{
Advert ad = e.UserState as Advert ;
adsToShowVertical.Add(ad );
}
}
I have not used the Telerik controls, but I suspect that if you change the following code in your View Model
public ReadOnlyObservableCollection<Advert> SquareAdsVertical
{
get
{
if (AdsManager.VerticalAds == null)
{
return null;
}
return new ReadOnlyObservableCollection<Advert>(AdsManager.VerticalAds);
}
}
To the following
private ReadOnlyObservableCollection<Advert> _readonlyAds;
public ReadOnlyObservableCollection<Advert> SquareAdsVertical
{
get
{
if (AdsManager.VerticalAds == null)
{
return null;
}
else if (_readonlyAds == null)
{
// Only one instance of the readonly collection is created
_readonlyAds = new ReadOnlyObservableCollection<Advert>(AdsManager.VerticalAds);
}
// Return the read only collection that wraps the underlying ObservableCollection
return _readonlyAds;
}
}
You need to return only one instance of the read only collection created from your observable collection. If you change a value in the Observable list, your control will be refreshed through the readonly collection.

How to add validation to PropertyGrid's CollectionEditor?

I'm using PropertyGrid to edit an object containing a collection.
Collection is edited using the CollectionEditor.
I have to make sure elements in collection are unique.
How can I add validation to CollectionEditor:
By either overloading CollectionEditor's OnFormClosing
Or adding validation for creating/editing items?
You can create your own collection editor, and hook into events on the default editor's controls. You can use these events to, say, disable the OK button. Something like:
public class MyCollectionEditor : CollectionEditor
{
private static Dictionary<CollectionForm, Button> okayButtons
= new Dictionary<CollectionForm, Button>();
// Inherit the default constructor from CollectionEditor
public MyCollectionEditor(Type type)
: base(type)
{
}
// Override this method in order to access the containing user controls
// from the default Collection Editor form or to add new ones...
protected override CollectionForm CreateCollectionForm()
{
CollectionForm collectionForm = base.CreateCollectionForm();
collectionForm.FormClosed +=
new FormClosedEventHandler(collectionForm_FormClosed);
collectionForm.Load += new EventHandler(collectionForm_Load);
if (collectionForm.Controls.Count > 0)
{
TableLayoutPanel mainPanel = collectionForm.Controls[0]
as TableLayoutPanel;
if ((mainPanel != null) && (mainPanel.Controls.Count > 7))
{
// Get a reference to the inner PropertyGrid and hook
// an event handler to it.
PropertyGrid propertyGrid = mainPanel.Controls[5]
as PropertyGrid;
if (propertyGrid != null)
{
propertyGrid.PropertyValueChanged +=
new PropertyValueChangedEventHandler(
propertyGrid_PropertyValueChanged);
}
// Also hook to the Add/Remove
TableLayoutPanel buttonPanel = mainPanel.Controls[1]
as TableLayoutPanel;
if ((buttonPanel != null) && (buttonPanel.Controls.Count > 1))
{
Button addButton = buttonPanel.Controls[0] as Button;
if (addButton != null)
{
addButton.Click += new EventHandler(addButton_Click);
}
Button removeButton = buttonPanel.Controls[1] as Button;
if (removeButton != null)
{
removeButton.Click +=
new EventHandler(removeButton_Click);
}
}
// Find the OK button, and hold onto it.
buttonPanel = mainPanel.Controls[6] as TableLayoutPanel;
if ((buttonPanel != null) && (buttonPanel.Controls.Count > 1))
{
Button okayButton = buttonPanel.Controls[0] as Button;
if (okayButton != null)
{
okayButtons[collectionForm] = okayButton;
}
}
}
}
return collectionForm;
}
private static void collectionForm_FormClosed(object sender,
FormClosedEventArgs e)
{
CollectionForm collectionForm = (CollectionForm)sender;
if (okayButtons.ContainsKey(collectionForm))
{
okayButtons.Remove(collectionForm);
}
}
private static void collectionForm_Load(object sender, EventArgs e)
{
ValidateEditValue((CollectionForm)sender);
}
private static void propertyGrid_PropertyValueChanged(object sender,
PropertyValueChangedEventArgs e)
{
ValidateEditValue((CollectionForm)sender);
}
private static void addButton_Click(object sender, EventArgs e)
{
Button addButton = (Button)sender;
ValidateEditValue((CollectionForm)addButton.Parent.Parent.Parent);
}
private static void removeButton_Click(object sender, EventArgs e)
{
Button removeButton = (Button)sender;
ValidateEditValue((CollectionForm)removeButton.Parent.Parent.Parent);
}
private static void ValidateEditValue(CollectionForm collectionForm)
{
if (okayButtons.ContainsKey(collectionForm))
{
Button okayButton = okayButtons[collectionForm];
IList<MyClass> items = collectionForm.EditValue as IList<MyClass>;
okayButton.Enabled = MyCollectionIsValid(items);
}
}
private static bool MyCollectionIsValid(IList<MyClass> items)
{
// Perform validation here.
return (items.Count == 2);
}
}
You will also need to add an Editor attribute to you collection:
class MyClass
{
[Editor(typeof(MyCollectionEditor),
typeof(System.Drawing.Design.UITypeEditor))]
List<Foo> MyCollection
{
get; set;
}
}
NOTE: I found that the value of items in removeButton_Click was not correct - so some tweaking may need to take place.
Try collectionForm.Context.Instance and typecast it to your data type this should do the trick.

Categories

Resources