Using a NodeView+NodeStore to display hierarchical data - c#

I'd like a NodeView to display some hierarchical data, something like this:
Father Mother
====== ======
Jon Ann
+Sons
+--Jon 20
+--Dave 10
+Daughters
+--Ann
Ron Mary
Paul Eve
+Sons
+--Bob 4
"Sons" and "Daughters" should not be shown if they are empty.
I have created three bussiness classes: Parents, Son and Daughter, and I'm creating TreeNode subclasses to display them.
I designed a Window with Monodevelop designer, and recreated the nodeView with code. The nodeview doesn't display anything at all, and I would like to know why. Here's the code, in a single file so anyone can test it:
using System;
using Gtk;
using System.Collections.Generic;
using System.Linq;
namespace Family.Model
{
public class Son {
public string Name {get;set;}
public string Age {get;set;}
public Son(string n,string a) {
Name=n;Age=a;
}
}
public class Parents
{
public string Father {get;set;}
public string Mother {get;set;}
public List<string> Daughters {get;set;}
public Dictionary<string,Son> Sons {get;set;}
public Parents() {
Daughters=new List<string>();
Sons=new Dictionary<string, Son>();
}
}
}
namespace Family.View
{
using Family.Model;
[TreeNode (ListOnly=false)]
public class ParentsNode:TreeNode
{
private Parents parents;
public ParentsNode (Parents p):base()
{
this.parents=p;
DaughtersRoot rootd=new DaughtersRoot();
SonsRoot roots=new SonsRoot();
if (p.Sons.Count>0) {
this.AddChild (roots);
p.Sons.Values.ToList ().ForEach (x=>roots.AddChild(new SonNode(x)));
}
if (p.Daughters.Count>0) {
this.AddChild (rootd);
p.Daughters.ForEach (x=>rootd.AddChild(new DaughterNode(x)));
}
OnChanged ();
}
[Gtk.TreeNodeValue(Column=0) ]
public string Father {
get { return parents.Father;}
set { parents.Father=value;OnChanged ();}
}
[Gtk.TreeNodeValue (Column=1)]
public string Mother {
get { return parents.Mother; }
set { parents.Mother=value;OnChanged ();}
}
}
[TreeNode(ListOnly=false)]
public class DaughtersRoot:TreeNode
{
[Gtk.TreeNodeValue(Column=0) ]
public string Label {
get {return "Daughters"; }
}
}
[TreeNode(ListOnly=false)]
public class SonsRoot:TreeNode
{
[Gtk.TreeNodeValue(Column=0) ]
public string Label {
get {return "Sons"; }
}
}
[TreeNode(ListOnly=false)]
public class DaughterNode:TreeNode {
private string mName;
public DaughterNode(string s):base() {
this.Name=s;
}
[Gtk.TreeNodeValue(Column=0) ]
public string Name {
get {return mName;}
set {mName=value;OnChanged ();}
}
}
[TreeNode(ListOnly=false)]
public class SonNode:TreeNode {
private Son son;
public SonNode(Son s):base() {
this.son=s;
OnChanged ();
}
[Gtk.TreeNodeValue(Column=0)]
public string Name {
get { return this.son.Name; }
set {son.Name=value;OnChanged ();}
}
[Gtk.TreeNodeValue(Column=1)]
public string Age {
get { return this.son.Age; }
set {son.Age=value;OnChanged ();}
}
}
public class MainWindow: Gtk.Window
{
private global::Gtk.ScrolledWindow GtkScrolledWindow;
private global::Gtk.NodeView treeFamily;
private NodeStore storeParents=new NodeStore(typeof(ParentsNode));
protected virtual void Build ()
{
global::Stetic.Gui.Initialize (this);
// Widget MainWindow
this.Name = "MainWindow";
this.Title = global::Mono.Unix.Catalog.GetString ("MainWindow");
this.WindowPosition = ((global::Gtk.WindowPosition)(4));
this.GtkScrolledWindow = new global::Gtk.ScrolledWindow ();
this.GtkScrolledWindow.Name = "GtkScrolledWindow";
this.GtkScrolledWindow.ShadowType = ((global::Gtk.ShadowType)(1));
this.treeFamily = new global::Gtk.NodeView ();
this.treeFamily.CanFocus = true;
this.treeFamily.Name = "treeFamily";
this.GtkScrolledWindow.Add (this.treeFamily);
this.Add (this.GtkScrolledWindow);
if ((this.Child != null)) {
this.Child.ShowAll ();
}
this.DefaultWidth = 400;
this.DefaultHeight = 300;
this.Show ();
this.DeleteEvent += new global::Gtk.DeleteEventHandler (this.OnDeleteEvent);
}
public MainWindow (): base (Gtk.WindowType.Toplevel) {
Build ();
Parents p=new Parents();
p.Father="Bob";
p.Mother="Mary";
storeParents.AddNode (new ParentsNode (p));
p=new Parents();
p.Father="Ron";
p.Mother="Ann";
p.Sons.Add ("David",new Son("David","20"));
p.Sons.Add ("Matt",new Son("Matt","10"));
p.Daughters.Add ("Elaine");
p.Daughters.Add ("Kate");
storeParents.AddNode (new ParentsNode(p));
this.treeFamily=new NodeView(storeParents);
Gtk.TreeViewColumn fatherColumn = new Gtk.TreeViewColumn
("Father",new CellRendererText(),"text",0);
Gtk.TreeViewColumn motherColumn = new Gtk.TreeViewColumn
("Mother",new CellRendererText(),"text",1);
treeFamily.AppendColumn (fatherColumn);
treeFamily.AppendColumn (motherColumn);
treeFamily.ShowAll ();
}
protected void OnDeleteEvent (object sender, DeleteEventArgs a) {
Application.Quit ();
a.RetVal = true;
}
}
class MainClass
{
public static void Main (string[] args) {
Application.Init ();
MainWindow win = new MainWindow ();
win.Show ();
Application.Run ();
}
}
}

Gtk model binding works quite different than other toolkits. That being said, here is what you need to know for starters:
There are several types of "controls" or widgets as they are called in GTK that can display matrix/list data:
NodeView (easiest)
TreeView
Now, AFAIC, NodeView only exists in GTK#, that is, the .Net bindings. TreeView is available in all other bindings and is part of the GTK+ Core. The difference is that somehow, NodeView is easier to use but more limited.
You'll want to use NodeView if you only need to display list data, that is, no hierarchical data (as you seem to need though)
If you need to display hierarchical data then you'll use a TreeView.
Whichever you use, you'll also have to set up what you need in that widget to display, your columns for example. The difference between a NodeView/TreeView cells in GTK and other basic toolkits is that a NodeView column for example can display other widgets inside its cell instead of just text, so you could have one column that has a cell that displays a progress bar, or a checkbox. The widgets that you use inside these cells are called CellRenderers and there are CellRendererText, CellRenderToggle, etc.
Now, these controls are "bound" to data by "stores", for example:
NodeStore
ListStore
TreeStore
Which one you'll use depends on your needs again and the type of widget you are going to use, so if you only need to display simple data use NodeStore, if you need to display hierarchical data use a TreeStore. One powerful thing about these data stores is that you can store in them not only the data that is shown on the NodeView/TreeView widget but any other data that does not necessarily needs to be displayed, you can even store objects, for example, you could have a store with 4 columns of which 3 are shown on the widget and the fourth keeps an instance of the full object.
In this link theres samples for each case I mentioned that you can try. I think that you should be using the "Tree" model to accomplish what you want and not the "Node".
GTK is a powerful toolkit but sometimes its hard to understand how it works. I hope this intro works for you.

Related

Trying to create a shopping cart in windows form c#. Specifically, adding a new row into datagridview from a button click in another form

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

MVVM : RaisePropertyChange on another item in the same list

I am using MVVM Light in a (pretty simple) WPF project.
I have a list of versions, and for each of them there is a button "activate" and "archive". Only one version can be active.
When clicking on "activate", the software must archive the currently active version, and activate the selected one.
How would you modelize this ? I'm currently using a pretty ugly solution : the selected version re-instantiates the "active version" and archives it, so obviously the previously-active version isn't "refreshed".
The main window contains a list of versions, displayed in a ListBox (see this question).
public class MainWindowViewModel : ViewModelBase
{
public MainWindowViewModel()
{
this.InstalledVersions = InstalledVersionViewModel.GetInstalledVersions();
}
public ObservableCollection<InstalledVersionViewModel> InstalledVersions { get; set; }
}
The InstalledVersionViewModel is (simplified) like this :
public class InstalledVersionViewModel : ViewModelBase
{
public InstalledVersionViewModel()
{
this.HandleActivateVersionCommand = new RelayCommand<RoutedEventArgs>(e => { this.ActivateVersion(); });
this.HandleArchiveVersionCommand = new RelayCommand<RoutedEventArgs>(e => { this.ArchiveVersion(); });
}
public string FolderPath { get; set; }
public RelayCommand<RoutedEventArgs> HandleActivateVersionCommand { get; private set; }
public RelayCommand<RoutedEventArgs> HandleArchiveVersionCommand { get; private set; }
public string VersionNumber { get; set; }
public static InstalledVersionViewModel GetCurrentVersion()
{
return GetVersionInfos(baseInstallPath); // returns the currently-active version
}
public static ObservableCollection<InstalledVersionViewModel> GetInstalledVersions()
{
var list = new ObservableCollection<InstalledVersionViewModel>();
// snip : fill the list from detected versions
return list;
}
private void ActivateVersion()
{
// snip
GetCurrentVersion().Archive();
// snip
}
private void ArchiveVersion()
{
// snip
}
}
The problem is in the ActivateVersion() method : I'm getting a new version instance to archive it, so obviously the version instance in the list is never aware of this change. But I don't know how to change the behavior to archive the version in the list instead. I'm pretty sure there should be either some kind of messaging system, a wrapper or an overarching structure, but I can't quite put my finger on it.
Thanks !
To me, it should be handled in the MainViewModel. For instance, add a property IsActive to your InstalledVersionViewModel, and subscribe to the PropertyChanged event from your MainViewModel. When the event is raised, browse your InstalledVersions list to find the previously active item, and call the Archive method on it.
To subscribe to the event, simply browse your list after creating it:
public MainWindowViewModel()
{
this.InstalledVersions = InstalledVersionViewModel.GetInstalledVersions();
foreach (var version in this.InstalledVersions)
{
version.PropertyChanged += this.VersionPropertyChanged;
}
}
Then, in the event, check which property has been changed:
private void VersionPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsActive")
{
var changedVersion = (Version)sender;
// Checks that the version has been activated
if (changedVersion.IsActive)
{
// Finds the previously active version and archive it
foreach (var version in this.InstalledVersions)
{
if (version.IsActive && version != changedVersion)
{
version.Archive();
}
}
}
}
}

How get determined buttons from class

this is not long story!! just it seems to be long ;)
in my app I have user access, it means access to a button relate to its user access scope.
in winform layer: I have a form, it shows all of the determined buttons' name in partitioned checkedListboxes. I dont want fill the form manually. I want create checkedListboxes by code. to get their items'text, I have below planing:
clssMenu_Item: I can save name and text property of one button in this class.
public class clssMenu_Item
{
public string name;
public string text;
}
clssMenu_List: it give me 2D generic List<clssMenu_Item>. all of the buttons in one form will be in a object of this class.
public class clssMenu_List
{
public clssMenu_List ()
{
// I dont know how fill private variables
}
#region private variables
private List<clssMenu_Item> _main ; // buttons in main form
private List<clssMenu_Item> _accountancy; //buttons in accountancy form
private List<clssMenu_Item> _management; //buttons in management form
#endregion
#region public properties
public List<clssMenu_Item> main
{ get { return _main; } }
public List<clssMenu_Item> accountancy
{ get { return _accountancy; } }
public List<clssMenu_Item> management
{ get { return _management; } }
#endregion
}
the buttons in each forms have a common character in their Name property. For example all of the determined buttons in Main form are started with ''Mbtn'', so there isn't any same buttons' Name between forms.
now I dont know how fill private variables in clssMenu_List. then I could use it in my facade layer.
thanks for your attention my friend!! please help me to solve it
I would create a separated helper class that extracts all buttons from a form.
public static class FormHelper
{
public static Button[] GetButtonsFromForm(Form form)
{
// ...
}
}
I would create properties instead of fields:
public class clssMenu_Item
{
public string Name {get;set;}
public string Text {get;set;}
}
A method to create menu_items:
public IEnumerable<clssMenu_Item> GetMenuItemsFromForm(Form form)
{
// convert the buttons to menu_items
return from button in FormHelper.GetButtonsFromForm(form);
select new clssMenu_Item { Name = button.Name, Text = button.Text };
}
Next I would add all buttons to the right list.
public void Fill()
{
clssMenu_List lst = new clssMenu_List();
clssMenu_List.main.AddRange(GetMenuItemsFromForm(mainForm));
clssMenu_List.accountancy.AddRange(GetMenuItemsFromForm(accountancyForm));
clssMenu_List.management.AddRange(GetMenuItemsFromForm(managementForm));
}
Don't forget to create the list in you class:
private List<clssMenu_Item> _main = new List<classMenu_Item>(); // buttons in main form
private List<clssMenu_Item> _accountancy = new List<classMenu_Item>(); //buttons in accountancy form
private List<clssMenu_Item> _management = new List<classMenu_Item>(); //buttons in management form
Personally:
I would store them in a dictionary because you can access them by name. And I would not create properties of List-types. I'd rather create Add/Remove methods.

Add more behaviour without creating new classes

This was the question asked in an interview.
There is a Label with a property Text
In one page a label is simple Label, in other pages it may handle any one or combination of the below actions
Clickable
Resizable
Draggable
How do you design this label component that applies OOP design Principle & Design Pattern?
I said that I would create the following:
public class Label
{
public string Text{get;set;}
}
public interface IClickable
{
void Click();
}
public interface IDraggable
{
void Drag();
}
public interface IResizable
{
void Resize();
}
So that if the client want Resizable Label
public class ResizableLabel:Label,IResizable
{
....
}
same way ClickableLable, DraggableLabel
However, I feel that this is the incorrect approach, because I do not want to add those concrete classes. I want to avoid having ClickableAndDraggableLabel or ClickableDraggableResizableLabel.
Is there any design pattern that would solve this problem without adding these concrete classes?
I would use Decorator pattern. It is used extensivelly in .net world for different kind of streams, that allow you to compose encrypted, zipped, text stream wrappers for byte stream, for example. class diagram is taken from wiki
Example for you situation is not so trivial in implementation, but usage doen't require another classes for new compising behavior:
// Define other methods and classes here
public class Label
{
public string Text{get;set;}
public virtual void MouseOver(object sender, EventArgs args) { /*some logic*/ }
public virtual void Click(object sender, EventArgs args) { /*some logic*/ }
//other low level events
}
public class ClikableLabel : Label
{
private Label _label;
public ClikableLabel(Label label)
{
_label = label;
}
public override void Click(object sender, EventArgs args)
{
//specific logic
_label.Click(sender, args);
}
}
public class DraggableLabel : Label
{
private Label _label;
public DraggableLabel(Label label)
{
_label = label;
}
public override void Click(object sender, EventArgs args)
{
//specific logic
_label.Click(sender, args);
}
}
public class ResizableLabel : Label
{
private Label _label;
public ResizableLabel(Label label)
{
_label = label;
}
public override void MouseOver(object sender, EventArgs args)
{
//specific logic
_label.MouseOver(sender, args);
}
public override void Click(object sender, EventArgs args)
{
//specific logic
_label.Click(sender, args);
}
}
now you can
var clickableDragableLabel = new ClikableLabel(new DraggableLabel(new Label{Text = "write me!"}));
var makeItResizable = new ResizableLabel(clickableDragableLabel);
I don't think Interface can resolve your problem.
I would make something more like this:
First, define an enum which list all your action:
public Enum LabelAction{ None = 0, Clickable = 1, Resizable = 2, Draggable = 4 }
For having multiple Enum defined, you can look this links:
How do you pass multiple enum values in C#?
Enumeration Types as Bit Flags
Then define a member in your class Label, taking an action:
public class Label
{
private readonly LabelAction _action;
private string Text { get; set; }
public class Label(string text)
: Label(text, LabelAction.None) { }
public class Label(string text, LabelAction action)
{
this.Text = text;
this._action = action;
}
public bool CanClick
{
get
{
return this._action & LabelAction.Clickable == LabelAction.Clickable;
}
}
public bool CanResize { get { return this._action & LabelAction.Resizable == LabelAction.Resizable ;} }
public bool CanDrag { get { return this._action & LabelAction.Draggable == LabelAction.Draggable ;} }
public Click()
{
if(this.CanClick) { /* click */ }
else { throw new Exception("Not clickable");}
}
public Drag()
{
if(this.CanDrag) { /* drag */ }
else { throw new Exception("Not draggable");}
}
public Resize()
{
if(this.CanResize) { /* resize */}
else { throw new Exception("Not resizable");}
}
}
Usage:
var simpleLabel = new Label("simple");
var clickable = new Label("clickable", LabelAction.Clickable);
var clickableDraggable = new Label("clickable and draggable", LabelAction.Clickable | LabelAction.Draggable);
public void DoEvent(Label label)
{
if(label.CanClick) label.Click();
if(label.CanDrag) label.Drag();
if(label.CanResize) label.Resize();
}
If you need to add an action, you will have to add one item to the Enum LabelAction, one method CanDo() and one method Do() to the Label class. Not so much so.
I would just have boolean properties for CanClick, drag, and resize, all default to true, and falsed as required (or as inherited).
constructor as follows
public Label(bool canClick = true, bool canDrag = true, bool canResize = true){}
Chances are if they're extending a class once, its going to be extended further at a later date
Well you can have a base class that implement all the interface and delegate their behaviour to concretes strategy classes.
Then you would have a NullDraggable, nulResizeable,NullClickable by default that do nothing (so your base label is not clickable, resizable and dragrable)
Then you create different strategy, like Clickable, DoubleClickable, WidthResizeable etc...
You then pass the combination you want to your class.
This way you obtain a lot of little strategy that are easy to reuse in other component with the same interface.
You can have multiple behaviour by using a composite pattern (for example you can have clickable and doubleclickable togheter)
This probably would be a little too ingeneered though
I think you're over-thinking what the interviewer must have had in his mind. If the case is as simple and practical, to avoid the complexity of over abstraction, then this would suffice:
public class Label
{
public string Text{get;set;}
}
public class ComlexLabel : Label
{
Click();
Drag();
Resize();
}
You can do any operation on it. Now if for a challenge you need only one concrete instance and need separate type of objects to be able to do only a combination of these things, its again simple - only that this time you have to create similar prototypes/interfaces:
public class Label
{
public string Text{get;set;}
}
public interface Clickable
{
Click();
}
public interface Resizable
{
Resize();
}
public interface Dragable
{
Drag();
}
public interface ClickableDragable : Clickable, Draggable
{
}
public interface ClickableResizable : Clickable, Resizable
{
}
public interface ResizableDragable : Resizable, Draggable
{
}
public interface ClickableDragableResizeable : Resizable, Clickable, Draggable
{
}
public class ComlexLabel : Lable, ClickableDragableResizeable
{
Click();
Drag();
Resize();
}
Now you can have instances of ComlexLabel by making the type that gives the required feature. Like:
ResizableDragable rd = new ComlexLabel();
ClickableResizable cr = new ComlexLabel();
ClickableDragableResizeable cdr = new ComlexLabel();
Now rd, cr and cdr have different capabilities. And only one concrete instance behind them. To prevent the clients from getting full privilege by doing
var cdr = new ComplexLabel();
you should make the ComplexLabel constructor private and assign the task to some factory. Like
var rd = Factory.GetResizableDragableLabel();
Now rd must be just ResizableDragable with no Click functionality..
I think for this scenario, there is no need to re-invent the wheel. Even though the question explicitly asks for OOP it is not explicitly asking you to ignore Component Model programming nor event based behaviors.
That's why I would follow an approach that allows a division of responsibilities where Label is responsible to notify when it is being clicked or dragged and SomeOtherComponent might or might not listen to such notification (event) in order to perform other logic.
Please take a look at the links below for examples of the approach of event dispatching for those user actions :
Drag and Drop
Label Class
Regards,

How do I "Invalidate" after adding items to a collection property?

I am creating a Custom control in where I am creating a property of the type "List"
Sections is a public class which has 4 properties.
The code in the control looks as follows:
public partial class genericGauge : Control
{
public genericGauge()
{
InitializeComponent();
}
// Stripped out code not needed for this issue question.
private List<Sections> indicators = new List<Sections>();
public List<Sections> Indicators
{
get
{
return indicators;
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
// Stripped out code not needed for this issue question.
}
}
The Sections Class is as follows:
public class Sections
{
private string header = "Section1";
public string Header
{
get {return header;}
set
{
header = value;
}
}
private float startvalue = 0.0f;
public float StartValue
{
get { return startvalue; }
set
{
startvalue = value;
}
}
private float sweepvalue = 0.0f;
public float SweepValue
{
get { return sweepvalue; }
set
{
sweepvalue = value;
}
}
private Color sectioncolor = new Color();
public Color SectionColor
{
get {return sectioncolor;}
set
{
sectioncolor = value;
}
}
}
Everything seems to work fine except that when I add items to the collection at designtime using the property browsers typeeditor the control is not repainted to reflect what is added to the collection.
When I click outside the control on my testform it is repainted.
Usually with simple properties I would use Invalidate, but this seems not to be possible here.
I also tried with other collection types than List<> where it is allowed to have a set accessor, but Invalidate still wont be called. I assume that it means that the SET is never called.
I know how to get this to work with expandable properties but I have no luck finding how to make this update with collections.
I hope someoone can help me out.
thanks in advance.
Instead of using the class List, use the class ObservableCollection, and use that to get notified when a new section is added or removed from the list.
private ObservableCollection<Sections> indicators = new ObservableCollection<Sections>();
public IList<Sections> Indicators
{
get
{
return indicators;
}
}
public genericGauge()
{
InitializeComponent();
this.indicators.CollectionChanged += this.IndicatorsCollectionChanged;
}
private void IndicatorsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// possibly inspect the NotifyCollectionChangedEventArgs to see if it's a change that should cause a redraw.
// or not.
this.Invalidate();
}
When using your example exactly as it was the Indicators property was not available for editing in the property window. So I made a few changes to it.
I added a new class:
// Added this class to deal with the Sections class
public class SectionObservable : ObservableCollection<Sections>
{
// Added a few methods here for creating a designtime collection if I need to.
}
Then I made the change as you suggested
public genericGauge()
{
InitializeComponent();
this.indicators.CollectionChanged += this.IndicatorsCollectionChanged; // your suggestion
}
And made the property like this instead:
private SectionObservable indicators = new SectionObservable(); // using the SectionObservable class instead
public SectionObservable Indicators // using the SectionObservable class instead
{
get
{
return indicators;
}
}
private void IndicatorsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) // your suggestion
{
this.Invalidate();
}
And now works as a charm.
Thank you very much. I appreciate to see that it IS possible to get help this fast. I like this forum alot.

Categories

Resources