Winforms Bubbling Events - c#

I want to bubble an event from a class to a Custom Usercontrol.
My Usercontrol is basically a datasheet.
Sheet
-> List<Row> Rows
->List<Cell> Cells
My UC(Sheet) has a list of Rows(Class) with each Row having a list of Cells(Class). My question is.. How can i raise an event from a Cell that the sheet can catch??

Your cells can raise an event on the sheet, if they are aware of the sheet. For instance:
{
... some code ...
Sheet.RaiseCellChanged(this.CellId);
}
Of course then each cell must have a way of referencing the sheet that it's on (using a Sheet property in the example above).
Another, more messy, option would be to have the Sheet subscribe to events on all individual cells. That would be more work, but then at least the Cells don't have to be aware of the Sheet they are on.
EDIT:
To answer your additional question, performance-wise there wouldn't really be a difference. The code would be easier to write using the first approach, simply also because your Sheet -- or any other object -- would only have to subscribe to a single event to receive data from any changing cell (assuming that that is what you want).

It is not really possible in WinForms to bubble events so you probably will need to some functionality on a Sheet level to add/remove a row or a cell.
By making it up to sheet you will be able to subscribe/unsubscribe to events from cells when created/removed and will also be able to hide the internal Sheet's structure/creation process from consumers:
public class Sheet
{
....
public void AddRow(object[] values)
{
var row = new Row();
foreach(var val in values)
{
var cell = new Cell(val);
cell.CellChanged += MyCellChangedHandler;
}
}
}
public class Cell
{
....
public Cell(object value)
{
var eventProvider = value as IMyEventProvider; //can be anything you want, INotifyPropertyChanges?
if (eventProvider != null)
eventProvider.SomeEvent += SomeEventHandler;
}
public SomeEventHandler(...)
{
if (CellChanged != null) CellChanged(...);
}
}
By doing this you will avoid cells knowing about the sheets and how to call sheets and which methods, etc (it is clearly not their concern), cells just raise events when they change, if anyone listens (sheet) they don't care.

Related

UITableView with Event Handler

So in my app I'm using a UITableView with a custom UITableViewCell to display a list of 'CustomItems' that contains a UISegmentedControl as an On/Off switch. Changing the value of a UISegmentedControl in cell 2, should turn on/off CustomItem[2], etc.
The UISegmentedControl performs an action for a CustomItem when the value is changed in a worker thread. The code I have works fine 99% of the time - in fact I have never seen it misbehaving. However we have customers who have reported that the UISegemented control is SOMETIMES not controlling the correct item.
In my UITableViewSource 'GetCell()' method, I have code that looks similar to this:
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
var cell = tableView.DequeueReusableCell(cellIdentifier) as CustomCell ?? new CustomCell(cellIdentifier);
. . .
// Handle the UISegementedControl (On/Off switch) value changed event
cell.segmentControl.ValueChanged += delegate
{
// Do some stuff
cell.outputControl.TintColor = cell.outputControl.SelectedSegment == 0 ? UIColor.Gray : UIColor.Red;
var currentItem = tableItems[cell.IndexPath.Row];
TurnSomethingOnOrOff(currentItem);
}
}
private void TurnSomethingOnOrOff(CustomItem currentItem)
{
var worker = new BackgroundWorker();
worker.DoWork += (sender, args) => {
// turn currentItem on or off
};
worker.RunWorkerAsync ();
}
What the user is seeing is that, on occasion, changing the value of the UISegementedControl in cell 1, is turning both CustomItem[0] and CustomItem[1] on or off.
I'm not sure how to get my app into the state that my customer is able to (they're not entirely sure either, it seems to happen at random).
Could this be something to do with Garbage Collection or some other reason why my cells would be losing the correct Event Handler?
The list only has 2 or 3 items in it, so cells generally aren't being reused. I did test the cell reuse (by scrolling off screen so the cells have to be redisplayed), and all that happened was the ValueChanged event is triggered twice, but the wrong event was never triggered.

ObjectListView editing doesn't work

I'm trying to create a simple listbox with ObjectListView (WinForm, C#). The goal is to have a single value (a double) and a check box.
I want to be able to edit the double value by Single Click, so here are the relevant lines of code from my MyWindow.Designer.cs file (i've left out the default values for efficiency):
this.olvDepths = new BrightIdeasSoftware.ObjectListView();
this.olvColumn1 = ((BrightIdeasSoftware.OLVColumn)(new BrightIdeasSoftware.OLVColumn()));
...
this.olvDepths.CellEditActivation = BrightIdeasSoftware.ObjectListView.CellEditActivateMode.SingleClick;
this.olvDepths.CheckBoxes = true;
this.olvDepths.CheckedAspectName = "IsDefault";
this.olvDepths.FullRowSelect = true;
//
// olvColumn1
//
this.olvColumn1.AspectName = "Depth";
this.olvColumn1.Text = "";
this.olvColumn1.IsEditable = true;
I then create a list of my class (ShieldingEntry) and use the olvDepths.SetObjects() with the list. My ShieldingEntry class looks like this:
public class ShieldingEntry
{
public double Depth { get; set; }
public bool IsDefault { get; set; }
}
However, when I click the field, it doesn't go into edit mode. I've also tried the DoubleClick, SingleClickAlways, and F2Only modes and they don't work either.
The Checkbox works fine.
************** I have additional information *********************
I've pulled and build the ObjectListView source, so I could step through it.
I put a breakpoint in the OLV StartCellEdit method and it gets called and appears to setup and select the control appropriately. It just never appears...
As I noted in the comments on the answer below, I've got this control on a tabbed dialog, and if I switch to another tab, then back, the control works fine.
What am I missing?
I've used ObjectListView before, and here is what I had to do:
Handle the CellEditStarting event. This event is raised when the cell goes into edit mode. Since OLV doesn't really have built-in editors, you have to make your own. Then handle the CellEditFinishing event to validate the data before putting it back into your model.
So first, handling the CellEditStarting event:
private void objlv_CellEditStarting(object sender, CellEditEventArgs e)
{
//e.Column.AspectName gives the model column name of the editing column
if (e.Column.AspectName == "DoubleValue")
{
NumericUpDown nud = new NumericUpDown();
nud.MinValue = 0.0;
nud.MaxValue = 1000.0;
nud.Value = (double)e.Value;
e.Control = nud;
}
}
This creates your editing control. If you want to make sure the size is right, you can set the size of the control (in this case a NumericUpDown) to the cell bounds using e.CellBounds from the event object.
This will show the editor when you click in the cell. Then you can handle the editor finished event to validate the data:
private void objlv_CellEditFinishing(object sender, CellEditEventArgs e)
{
if (e.Column.AspectName == "DoubleValue")
{
//Here you can verify data, if the data is wrong, call
if ((double)e.NewValue > 10000.0)
e.Cancel = true;
}
}
I don't think handling it is required, but its good practice to validate data from the user.
The editing control in the CellEditStarting event can be any control, even a user defined one. I've used a lot of user defined controls (like textboxes with browse buttons) in the cell editor.
[Edit]
I uploaded an example here dropbox link that seems to work. Might not be in the exact view as needed, but seems to do the job.
For anyone else with this problem. I had it specifically when trying to edit a 'null' value in a decimal? on the OLV on a tab page. Solution for me was to set UseCustomSelectionColors to 'False'. I didn't look elsewhere to see if it was reported as a bug. Seems like a bug.

Copy/Paste a listobject in excel lose the attached events

Let's create a ListObject and attach an event on it as msdn explain here: https://msdn.microsoft.com/en-us/library/eyfs6478.aspx
The code
In an application-level addin, code will look like above:
Worksheet worksheet = Globals.Factory.GetVstoObject(Globals.ThisAddIn.Application.ActiveWorkbook.Worksheets[1]);
Excel.Range cell = worksheet.Range["$A$1:$D$4"];
ListObject list1 = worksheet.Controls.AddListObject(cell, "list1");
list1.Selected += list1_SelectedDeselected;
list1.Deselected += list1_SelectedDeselected;
With something like that to see the triggered events :
private void list1_SelectedDeselected(Excel.Range Target)
{
Worksheet worksheet = Globals.Factory.GetVstoObject(Globals.ThisAddIn.Application.ActiveWorkbook.Worksheets[1]);
Excel.Range cell = worksheet.Range["$A$6"];
if (cell.Value2 == "foo")
{
cell.Value2 = "bar";
}
else
{
cell.Value2 = "foo";
}
}
Strange behavior
If you run this code within a add-in in excel, you'll see this and that
If you cut/paste this table within the same worksheet, every attached events will be triggered.
But if you cut/paste into another sheet, the events will not be attached anymore to the ListObject.
Are there a reason I don't know for this unpredictable behavior?
You need to use another VSTO object to subscribe to the events anew. The GetVstoObject should be used to get a new Worksheet object.

C# VSTO - dynamically set RibbonControl

I have a customized Ribbon in Word. The Ribbon has one comboBox: comboBox_recentConditions which was defined using the Designer - so it's initalized and is empty at load. Now, I would like to dynamically set this comboBox each time the Application_WindowActivate event is fired.
Each Word document has its own instance of class called RibbonControls:
class RibbonControls
{
private RibbonComboBox recentConditionComboBox;
public RibbonControls()
{
this.recentConditionComboBox = new RibbonComboBox();
}
public RibbonComboBox RecentConditionComboBox
{
get
{
return recentConditionComboBox;
}
set
{
recentConditionComboBox = value;
}
}
}
Now in Application_WindowActivate event i do the following:
static void Application_WindowActivate(Document doc, Window Wn)
{
Globals.Ribbons.SourceRibbon.comboBox_recentConditions = WordGate.docRibbonControls.RecentConditionComboBox;
}
The problem is that the Ribbon comboBox control doesn't changes, it's always empty, even after Application_WindowActivate is called.
I tested at run-time to see if each document indeed has its own comboBox with its items - which seems to work.
What am I missing?
To clear my question:
Let's say I have 3 items in the comboBox.Items. When clicking on it I see nothing, but if I add this:
MessageBox.Show(Globals.Ribbons.SourceRibbon.comboBox_recentConditions.Items.Count.ToString());
at the end of Application_WindowActivate it will print the number 3.
Thanks.
You cant do it that way. Please have a read on Ribbon callbacks
You have to use the getItemLabel event which will add items to combo box. If you want to load the combobox during WindowActivate then call Ribbon.Invalidate or Ribbon.InvalidateControl which will call all relevant ribbon callbacks.

WinForms DataGridView - Full text display and dataSource update

Earlier today i was suggested in here to use a DataGridView to print messages that needed a individual mark as read.
I followed the suggestion, and with some reading online i managed to bind it to my message list with the following results after some tweaking.
alt text http://img237.imageshack.us/img237/3015/datagridview.jpg
Currently i have 2 issues, the first one is that i didn't find a way to resize the row height to display the full message, and the second one is that when the list is updated, the DataGridView doesn't display the modifications.
Any way to solve both problems? Or do i need to use something other than DataGridView, and in that case what should i be using?
Also, is there any way to urls contained in the message to become clickable and be opened in the default browser?
EDIT
More info in relation to the binding.
Basically i have a class variable inside the form, and i do the initial binding with a button.
private void button1_Click(object sender, EventArgs e)
{
list.Add(new Class1() { Message = "http://www.google.com/", Read = false });
list.Add(new Class1() { Message = "Message way too long to fit in this small column width", Read = false });
dataGridView1.DataSource = list;
}
I then have another button that adds some more entries just to test it, and i know the list is properly updated, but there are no changes in the dataGridView.
EDIT 2
If i wasn't clear before i need for the width to be fixed, and the cell height that contains the long text to be enlarged and display the text in 2 lines
have you checked the options in the EditColumn using smart tag ?
you can add column of type
DataGridViewLinkColumn, set its Text property to Message
Try removing any value from width
and height properties for a
column. In this way, it will set the
column size (cell) size according to
the data size.
hope this helps
I'll take a stab and see if I can help.
First off the row height. There are two DataGridView Methods called AutoResizeRow and AutoResizeRows which will adjust the height of the row to fit the contents.
Can you show us how you are binding your data to the DataViewGrid and how the data might be modified? That will help with the modifications not updating.
As for the link, unfortunately I can't seem to find an object which handles this sort of thing natively. Most likely you will first have to decide if the text going into the DataGridView is a link (using a Regular Expression, if you were me). Second, display it differently in the DataGridView (underline it, make it blue). Third, put a click event on it and when that cell is clicked handle that by throwing it out to a browser. I will look a little further into it though since this seems like a lot of work (and I will keep my fingers crossed that someone knows better than I do).
Regarding the list not updating; there are two issues;
To notice add/remove, you need list binding events. The easiest way to do this is to ensure you use a BindingList<YourClass> rather than a List<YourClass>.
To notice changes to individual properties (in this context) you'll need to implement INotifyPropertyChanged on your type:
public class YourClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
private string message;
public string Message
{
get { return message; }
set { message = value; OnPropertyChanged("Message"); }
}
public bool isRead;
[DisplayName("Read")]
public bool IsRead
{
get { return isRead; }
set { isRead = value; OnPropertyChanged("IsRead"); }
}
}
For an example showing binding that to a list:
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
BindingList<YourClass> list = new BindingList<YourClass>();
DataGridView grid = new DataGridView();
grid.Dock = DockStyle.Fill;
grid.DataSource = list;
Button add = new Button();
add.Text = "Add";
add.Dock = DockStyle.Bottom;
add.Click += delegate
{
YourClass newObj = new YourClass();
newObj.Message = DateTime.Now.ToShortTimeString();
list.Add(newObj);
};
Button edit = new Button();
edit.Text = "Edit";
edit.Dock = DockStyle.Bottom;
edit.Click += delegate
{
if (list.Count > 0)
{
list[0].Message = "Boo!";
list[0].IsRead = !list[0].IsRead;
}
};
Form form = new Form();
form.Controls.Add(grid);
form.Controls.Add(add);
form.Controls.Add(edit);
Application.Run(form);
}

Categories

Resources