how to handle events from variable number of sources? - c#

I have such class (sorry about posible mistakes, i'm writing it right here.) Class is simplified for this example, it must be more complex of course.
class SP500Index {
SP500Index(List<OrderBook> stocks) {
foreach (var stock in stocks) {
stock.StockUpdated += stockUpdated; // how to handle?
}
}
}
So I have a lot of sources and I need to handle StockUpdated event from them. In handler I need to know index of stock in stocks list which raised the event. How to do that?
upd for perfomance reasons I don't want "sender look-up" instead I want index. Lookup is not trivial operation and likely involves Hashcode calculation Equals method call etc. Imagine how ofthen SP500 index changes...

This is not provided automatically.
But the StockUpdated event should look like
void StockUpdated (object sender, MyEventArgs e)
and you can cast senderto a stock and look it up in the original list. If you still need the index.
void stockUpdated (object sender, MyEventArgs e)
{
OrderBook stock = (OrderBook) sender;
....
}

It is good practice to use a signature of the form:
public delegate void CustomEventHandler(object sender, CustomEventArgs a);
In your event handler you can use sender to find out which object raised the event.
If you don't have a sender parameter then I don't think there is any (reasonable) way to find out which object raised the event.
Related
How to: Publish Events that Conform to .NET Framework Guidelines (C# Programming Guide)

If your benchmark shows that you need the index when the event occurs you can add the index as a property to OrderBook and when you add an element to the list, set this property. This value will be available to the event handler.
This will work assuming that you keep the OrderBook objects in a single List and do not do any rearranging of this original list. If you have multiple lists with each object stored on only one of those lists, then you could add an Owner property that references the list that it is stored on.
For example...
When you build List<OrderBook>:
...
OrderBook book = CreateOrderBook(...);
list.Add(book);
book.ListIndex = list.Count - 1;
// Assign Owner here if that is needed.
...
Or better, use a helper for managing the list that cares for the book keeping of the index update:
public class OrderBookManager
{
private List<OrderBook> list = new List<OrderBook>();
public void Add(OrderBook book)
{
list.Add(book);
book.ListIndex = list.Count - 1;
// Assign Owner here if that is needed.
}
// Make this read-only if you want to ensure the manager controls all updates to the list (better design) but use it this way for higher performance.
public List<OrderBook> List { get { return list; } }
}
The updated OrderBook:
public class OrderBook
{
...
public int ListIndex { get; set; }
}
And then an sample event handler using this index:
public void StockUpdated(object sender, MyEventArgs eventArgs)
{
OrderBook book = (OrderBook) sender;
//Here use book.ListIndex to access the original list element.
}
What is your reason for having that index? Everything you need should be in the object. If you use this to manipulate the original list (such as removing this item from the list) then you have the problem of recomputing the saved index of all the previously stored objects.
If you are maintaining a parallel list with other objects, then you perhaps should consider a different design.

You can define in delegate of that event sending source (which is basically the suggested guideline by Microsoft. Having object sender, in other words, like a first parameter of the delegate's signature).
After make (say) a cast and determine in some if/else the real object type.

Related

checking items in a list and take a decision

I want to build a program in Windows forms where the user can create a pizza by pressing buttons.
My problem is that when the user presses an ingredient more than once, the list will just increment. I tried various methods but they don't seem to work.
I have seen a solution using a for loop checking individual items in the list however I will have to implement that 19 times which is not really efficient (once for every button)
string check = "Thin Base";
if (My_Pizza.Contains(check))
{
My_Pizza.Items.Remove("ThinBase");
My_Pizza.Items.Add("Thin Base");
}
You have "ThinBase" and "Thin Base". Not the same thing.
Also, if an item is already in the list, you don't need to do anything. Simply invert your check
string check = "Thin Base";
if (!My_Pizza.Items.Contains(check))
{
My_Pizza.Items.Add(check);
}
General idea is not to hardcode string values, but create reusable method that will do what you need: check if certain value is already in the list and if it not, add this item to list. This will help you to avoid duplicate code.
In the button event handler you simply call this method and provide string value as parameter. I'm not sure how exactly you handle button clicks, but I would suggest creating single reusable method once again and acquire string value from button.Text property.
Here is code sample for you to demonstrate the idea.
private void OnButtonClick(object sender, EventArgs e)
{
Button clickedButton = (Button) sender;
if (clickedButton != null)
{
string buttonContent = clickedButton.Text;
CheckAndAdd(buttonContent);
}
}
private void CheckAndAdd(string valueToCheck)
{
if (!My_Pizza.Items.Contains(valueToCheck))
{
My_Pizza.Items.Add(valueToCheck);
}
}

Winforms Datagridview can't be refreshed from a delegate

I'm trying to load data from file to list and show that data immediately on Winforms' Datagridview. For that I've made the reading in another thread using Backgroundworker. The problem is, it only updates once and I can't get it to show more data. Not only that, when clicked, it tries to access element with -1 index, which of course doesn't exist, resulting in a crash.
Usually, from what I've seen, simply adding again same data to data source dataGridView1.DataSource = samelist; should work, but not in this case.
BackgroundWorker's work
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
//lotsofCode...
while (readData != null)
{
fooLists.Add(readData);
//someCalculations...
worker.ReportProgress();
}
}
BackgroundWorker's progressChanged
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.Invoke((MethodInvoker)delegate { UpdateGridView(); });
}
UpdateGridView Method
private void UpdateGridView()
{
if (fooLists.GetListById(1).calculatedList != null)
dataGridView1.DataSource = fooLists.GetListById(1).calculatedList;
}
Later on I've read some threads on stack, where one suggested using BindingSource as a "middleman", so now I have dataGridView1.DataSource = MyBindingSource; in the component initialization and tab1source.DataSource = fooLists.GetListById(1).calculatedList; instead of dataGridView1.DataSource. It certainly helped, as the list is now clickable as it should be, but still there are only few records on a list.
None of dataGridView1.Refresh(), dataGridView1.RefreshEdit() or dataGridView1.Update() helped, though made the list loading slightly fancier (probably due to the delay they introduced :) ).
I tried making some "protections" (semaphores, so the delegate isn't called again, while working; try-catches, though no exceptions are thrown there; data clearing before re-writing...) but the "better version" worked as poor as this one and it only darkened the code.
Am I missing a way to update the Datagridview control? Thanks in advance.
Although you didn't write it, but I think the reason that the items that you add to your dataSource are added to a collection that does not implement interface IBindingList. You probably use a simple list to hold your read data.
If your 'DataSourceimplements this interface, then after adding an item to your collection an event is raised. The class that holds theDataSource, whether it is aDataGridViewor aBindingSource` get notified about the changes in the list and update their contents accordingly.
Your solution would be to store your elements in an object of class System.ComponentModel.BindingList<T>.
Suppose the items you want to show are of class MyReadData
class MyForm : Form
{
public MyForm()
{
InitializeComponents();
this.myReadItems = new BindingList<MyReadData>();
this.MyBindingSource.DataSource = this.myReadItems;
// if not already done in InitializeComponents
this.MyDataGridView.DataSource = this.MyBindingSource;
}
private readonly BindingList<MyReadData> myReadItems;
// whenever needed, start the BackGroundWorker.
private void OnButtonReadFile_Click(object send, EventArgs e)
{
// create and start the backgroundworker
BackGroundWorkdr worker = ...
MyBackGroundWorkerParams params = ...
worker.RunWorkerAsync(params);
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
// I am certain the sender is my BackGroundWorker:
BackgroundWorker worker = (BackGroundWorker)sender;
MyBackGroundWorkerParams params = (MyBackGroundWorkerParams)e.Argument;
// do some work using the params
while (readData != null)
{
// some data read.
// dont't add the data to the list, just report the data that must been added to the list:
// someCalculations...
int percentProgress = ...
MyReadData dataToAddToGrid = ...
worker.ReportProgress(percentProgress, dataToAddToGrid);
}
private void bw_progressChanged(object sender, ProgressChangedEventArgs e)
{
// no need to call invoke, this is already the context of your forms thread
Debug.Assert(!This.InvokeReguired);
MyReadData dataToAdddToGrid = (MyReadData)e.UserState;
this.myReadItems.Add(dataToAddToGrid);
}
}
The main difference is that you should not let your BackgroundWorker to add data to the list of displayed data. The task of the BackGroundWorker is to read the data and to report to everyone who is interested what data has been read.
As it is the task of MyForm to display the read data, let MyForm decide which read data to display and in what format. This enhances reusage of both MyForm and MyBackGroundWorker: MyForm could display that that has been fetched in a different way, and MyBackGroundWorker could be used to inform others than MyForm to notify about read data.
Furthermore the display context of the progress changed event handler is the context of 'MyForm`, so an invoke is not needed.
You could also assign the IBindingList directly to the DataGridView, so without the use of a BindingSource. The only reason to keep a BindingSource is if you want access to the Current item, or if you want the freedom to fill your DataGridView with other items than the contents of your BindingList.
Finally: the most important part of the solution was that the items were added to an IBindingList.
System.Components.BindingList<T> is a class with limited functionality. If you want to order the rows in your DataGridView, or only show items that match some predicate, or combine items from several sources into one DataGridView, consider using Equin.ApplicationFramework.BindingListView
using Equin.ApplicationFramework;
public MyForm()
{
InitializeComponents();
this.myReadItems = new BindingListView<MyReadData>(this.components);
this.MyBindingSource.DataSource = this.myReadItems;
this.MyDataGridView.DataSource = this.MyBindingSource;
}
private readonly BindingListView<MyReadData> myReadItems;
private void bw_progressChanged(object sender, ProgressChangedEventArgs e)
{
MyReadData dataToAdddToGrid = (MyReadData)e.UserState;
this.myReadItems.Add(dataToAddToGrid);
// finished updating the list, DataGridView can be updated:
this.myReadItems.Refresh();
// this Refresh function allows you to change several items in the list
// without unnecessary intermediate updates of your BindingSource and DataGridView
}
Presto, that is all: free sorting or your columns by clicking on the column header. Consider examining their example project to see how filtering works and how to use several sources.
// Show only Brummies
this.myReadData.ApplyFilter(person => person.Address.City == "Birmingham");
// Remove the filter, show everyone again
this.myReadData.RemoveFilter();

Wrapping multiple server calls using IEnumerable and IEnumerator [duplicate]

I have a case where I have either a gridview/listbox/any type of items control and the number of items bound to the control is massive (easily around 5000+ mark).
Each of these items needs to have various attributes loaded from various web services. Obviously, reaching out to web services to process this amount of elements all at once is out of the question.
My question is, is it possible to postpone loading until these items are actually displayed to the user? As in, the user scrolls down and although the items have been present in the collection all along, they are processed only when they are actually physically rendered.
I've seen it done before, but I can't remember where exactly. It was a situation where lots of stock quotes were in a collection bound to a gridview, but their attributes (prices etc...) were empty until they were displayed for the first time (by scrolling to their respective position).
Hopefully this made (some) sense.
Any ideas on how to pull it off?
I would try a combination of lazy loading and asynchronous loading:
Use a virtualizing list-control. Create a ViewModel for your items and fill your list with instances of the ViewModel (one per line).
In your ViewModel, make properties that have a default-value that shows the user that the data not has been loaded. The first time one of these property is accessed, trigger loading the data asynchronous and fire INotifyPropertyChanged when the real data has been received.
This will give the user a nice experience and most of the tricky work will be done through the virtualizing list (in WPF this are ListBox,ListView, DataGrid...). Hope this helped.
class LineItemVM : INotifyPropertyChanged{
bool m_loadingTriggered;
string m_name="Loading...";
string m_anotherProperty="Loading...";
public string Name{
get{
TriggerLoadIfNecessary(); // Checks if data must be loaded
return m_name;
}
}
public string AnotherProperty{
get{
TriggerLoadIfNecessary(); // Checks if data must be loaded
return m_anotherProperty;
}
}
void TriggerLoadIfNecessary(){
if(!m_loadingTriggered){
m_loadingTriggered=true;
// This block will called before your item will be displayed
// Due to the m_loadingTriggered-member it is called only once.
// Start here the asynchronous loading of the data
// In virtualizing lists, this block is only called if the item
// will be visible to the user (he scrolls to this item)
LoadAsync();
}
}
...
Additional logic
As an idea, you could also make an outer asynchrounous loading thread that loads all data in background, but has a list for items that should be loaded with higher priority. The concept is the same as in the above example, but instead of loading data from your ViewModel-item, the TriggerLoadIfNecessary-method only adds the item in the high-priority list so that the potentially visible elements are loaded first. The question which version is better suited depends on the usage of the list. If it is probable that the user uses the full list and does not navigate quickly away, this extended version is better. Otherwise the original version is probably better.
Here is an event that will notify when user scrolls into the last screen of data:
using System.Windows;
using System.Windows.Controls;
public static class ScrollViewer
{
public static readonly RoutedEvent LastPageEvent = EventManager.RegisterRoutedEvent(
"LastPage",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(ScrollViewer));
private static readonly RoutedEventArgs EventArgs = new RoutedEventArgs(LastPageEvent);
static ScrollViewer()
{
EventManager.RegisterClassHandler(
typeof(System.Windows.Controls.ScrollViewer),
System.Windows.Controls.ScrollViewer.ScrollChangedEvent,
new ScrollChangedEventHandler(OnScrollChanged));
}
public static void AddLastPageHandler(UIElement e, RoutedEventHandler handler)
{
e.AddHandler(LastPageEvent, handler);
}
public static void RemoveLastPageHandler(UIElement e, RoutedEventHandler handler)
{
e.RemoveHandler(LastPageEvent, handler);
}
private static void OnScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (e.ViewportHeight == 0 || e.VerticalOffset == 0)
{
return;
}
var verticalSpaceLeft = e.ExtentHeight - e.VerticalOffset;
if (verticalSpaceLeft < 2 * e.ViewportHeight)
{
var scrollViewer = (System.Windows.Controls.ScrollViewer)sender;
scrollViewer.RaiseEvent(EventArgs);
}
}
}

Assigning a value to a global variable from inside of event handler?

I'm working on a program but an issue i was faced to keep me worried.I'm kind of novice and i'm building this program for a competition.The code where the problem lies is like following :
class Blabla : Usercontrol
{
public List<string> mainList;
public Blabla()
{
mainList = new List<string>();
something.DownloadStringCompleted += new DownloadStringCompletedEventHandler(xx_DownloadStringCompleted);
}
void xx_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
List<string> abc = SomeMethod(e.Result);
mainList = abc;
}
}
I try it.Even though "abc" variable has the value i want , mainList remains empty.I don't know why and how to make it work.That's why i need some hand.Thank you.
Variable abc has the value you want UNTIL you get out of your event handler, probably, when it gets deleted because it uses e.Result directly.
Familiarize yourself with .Clone() method and IClonable interface, and try creating a COPY of the list that is in question, not the reference.
If abc is a list, mainList will be set to the same list. You don't have to clone the list, it should stay active because there is a reference to it, and therefore it doesn't get garbage collected.
When you said that mainList was empty, did you look at it in the debugger immediately after setting it in the xx_DownloadStringCompleted method? Or are you looking at it somewhere else in your program?
I would guess that this is a threading issue. Does your event handler get called from a different thread? If so, you would need to add some synchronization logic in order to guarantee that mainList is available to your other thread.

Is storing an object reference in a controls Tag property OK

I'm creating a group of form controls for each object in a list, is it OK to store a reference to the object in the controls Tag property?
I'm doing this so I can have a generic Click event on the controls, so when they are clicked I can update a field in the object that they represent.
So the click handler will look something like this.
private void Item_Clicked(object sender, system.EventArgs e)
{
if(sender.GetType() == typeof(System.Windows.Forms.Label))
{
System.Windows.Forms.Label label = (System.Windows.Forms.Label)sender;
MyObject myObject = label.Tag;
myObject.Value = true;
}
}
Is this an acceptable thing to do in this situation, or is there a better way to handle this?
Yes this is legal to do and is one of the patterns the Tag property was designed for.
The biggest danger here is that another peice of code attempts to use the same Tag property for their own feature. This would create a race for the Tag property and lead to runtime bugs. A safer approach would be to create a private map between a Label and MyObject using a Dictionary instance.
private Dictionary<Label,MyObject> _map = new Dictionary<Label,MyObject>();
...
private void Item_Clicked(object sender, system.EventArgs e)
{
if(sender.GetType() == typeof(System.Windows.Forms.Label))
{
System.Windows.Forms.Label label = (System.Windows.Forms.Label)sender;
MyObject myObject = _map[label];
myObject.Value = true;
}
}
This approach has the extra overhead of a Dictionary but produces more reliable code (IMHO).
It's acceptable if it works for you. I've stored things in the tag properly like this before, and it works fine. Things to consider: the size of the object you're storing and the lifecyle of the object (could it be disposed of or destroyed between accesses).
Another approach, that I have used, is to store a "hint" that would help you retreive or recreate the object. For example, if it's a database object, store the Id property (maybe an integer or Guid) which is much [potentially] much smaller than the object itself.

Categories

Resources