Performance of refreshing listview - c#

I have a ListView-Control which holds several entries (about 300). When I open it, it starts up pretty quick. I do some things and call the btnRefresh_Click-Button event to fill data into the ListView.
public ListWords()
{
InitializeComponent();
lvwColumnSorter = new ListViewColumnSorter();
this.lstWords.Visible = true;
this.btnCheckLinks.Visible = true;
this.lstWords.ListViewItemSorter = lvwColumnSorter;
btnRefresh_Click(null, new EventArgs());
}
The event does this:
public void btnRefresh_Click(object sender, EventArgs e)
{
lstWords.Items.Clear();
foreach (var word in WordData.AllWords)
{
string[] lvi = {
word.Id.ToString(), word.Name, word.Link,
word.Status, word.Count.ToString()
};
lstWords.Items.Add(new ListViewItem(lvi));
}
}
So far so good. But if I actually PRESS the button to manually refresh the ListView, it takes around 4 to 5 seconds! I'ts even faster if I close the form and open a new one, then the refreshed data appears in an instant.
What is the cause for this behaviour? I can't figure out what the difference is between calling it programmatically in the constructor and calling it manually by a button press event. Thanks in advance!

Related

Inserting a huge dataset into C# combo box

I am trying to load a 10k row database XML export into combo box in C# but the time to open the window takes forever and I am sure 10k rows isnt that much to process, yet it still takes me about 20 seconds to finish maxing out one of my CPU threads. I tried creating a new thread in C# but got a cross-thread error. Any way for me to improve the loading time into 1 or 2 seconds?
public partial class AddGameWindow : Form
{
public AddGameWindow()
{
InitializeComponent();
Application.UseWaitCursor = true;
}
private void AddGameWindow_Load(object sender, EventArgs e)
{
var doc = XDocument.Parse(Properties.Resources.GameDB);
var rows = doc.Descendants("table").Select(el => new Game
{
Name = el.Element("title").Value,
Url = el.Element("link").Value,
Img = el.Element("imglink").Value
});
if (gamesList.Items.Count == 0 ) {
// for some reason fires again when window closed :(
int i = 0;
foreach (var row in rows)
{
//nejak to tam nahazet at to dlouho netrva
gamesList.Items.Insert(i, row.Name);
i++;
}
Application.UseWaitCursor = false;
}
}
}
class Game
{
public string Name;
public string Url;
public string Img;
}
You'll need to leverage BeginUpdate/EndUpdate if you're pushing a huge number of items into a list based control one at a time
Do heed the advice in the comments though; a combo of more than about 20 items is pretty unusable. Look at a "type in this textbox and use it as a filter", possibly with "only start filtering after the user typed 3 characters" and a "only start filtering after a delay of 500ms rather than filtering upon every keypress" type behavior..

Why isn't my entity framework deleting my record?

New to entity framework, but I would think this should be pretty simple.
My form load creates a context from my Entities. I create a list of clients and have a binding source that I assign clients to. The binding source is assigned to a Binding Navigator - clientBindingNavigator.
private void ClientExtForm_Load (object sender, EventArgs e)
{
_context = new IDVisitorEntities ();
List<IDVM.Client> clients = _context.Clients.ToList ();
clientBindingSource.DataSource = clients;
}
excerpt from ClientExtForm.Designer.cs
//
// clientBindingNavigator
//
this.clientBindingNavigator.AddNewItem = this.bindingNavigatorAddNewItem;
this.clientBindingNavigator.BindingSource = this.clientBindingSource;
this.clientBindingNavigator.CountItem = this.bindingNavigatorCountItem;
this.clientBindingNavigator.DeleteItem = this.bindingNavigatorDeleteItem;
this.clientBindingNavigator.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.bindingNavigatorMoveFirstItem,
this.bindingNavigatorMovePreviousItem,
this.bindingNavigatorSeparator,
this.bindingNavigatorPositionItem,
this.bindingNavigatorCountItem,
this.bindingNavigatorSeparator1,
this.bindingNavigatorMoveNextItem,
this.bindingNavigatorMoveLastItem,
this.bindingNavigatorSeparator2,
this.bindingNavigatorAddNewItem,
this.bindingNavigatorDeleteItem,
this.clientBindingNavigatorSaveItem});
When I click the Delete Button on the navigator tool bar the the ClientBindingSource.Count has been reduced by 1 .
private void clientBindingNavigatorSaveItem_Click (object sender, EventArgs e)
{
this.OnSave ();
}
public override void OnSave ()
{
foreach (ObjectStateEntry entry in _context.ObjectStateManager.GetObjectStateEntries (EntityState.Deleted))
{
// nothing shows up in this
}
foreach (ObjectStateEntry entry in _context.ObjectStateManager.GetObjectStateEntries (EntityState.Modified))
{
// when modified
}
foreach (ObjectStateEntry entry in _context.ObjectStateManager.GetObjectStateEntries (EntityState.Added))
{
// when adding this finds it
}
clientBindingSource.EndEdit ();
visitorHostsBindingSource.EndEdit ();
_context.SaveChanges ();
base.OnSave ();
}
It appears as though the navigator is removing the item from the collection.
Added info: It appears that in the navigator the DeleteItem button corresponds to the RemoveCurrent method (on click event calls it). Not sure how to tie in before the RemoveCurrent does it's thing.
What are my options for preforming the delete?
Removing an item from the clientBindingSource will have no effect on the item at the database level. You have to explicitly call _context.Clients.DeleteObject(deletedClient); You perform must all CRUD operations through the ObjectContext.
After looking around found some blogs that suggest to not use the default DeleteItem.
this.clientBindingNavigator.DeleteItem = null;//= this.bindingNavigatorDeleteItem;
In my case to make it clear for the BindingNavigator I replaced this.bindingNavigatorDeleteItem with a new button this.toolStripButton1 in the Items list.
this.clientBindingNavigator.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.bindingNavigatorMoveFirstItem,
this.bindingNavigatorMovePreviousItem,
this.bindingNavigatorSeparator,
this.bindingNavigatorPositionItem,
this.bindingNavigatorCountItem,
this.bindingNavigatorSeparator1,
this.bindingNavigatorMoveNextItem,
this.bindingNavigatorMoveLastItem,
this.bindingNavigatorSeparator2,
this.bindingNavigatorAddNewItem,
this.toolStripButton1,
this.clientBindingNavigatorSaveItem});
Creation of the new button looks like this:
//
// toolStripButton1
//
this.toolStripButton1.Image = ((System.Drawing.Image) (resources.GetObject ("bindingNavigatorDeleteItem.Image")));
this.toolStripButton1.RightToLeftAutoMirrorImage = true;
this.toolStripButton1.Name = "toolStripDeleteItem";
this.toolStripButton1.Size = new System.Drawing.Size(23, 22);
this.toolStripButton1.Text = "Delete";
this.toolStripButton1.Click += new System.EventHandler(this.toolStripButton1_Click);
The Click event then calls the RemoveCurrent (just like the default does) but I can get the current entity and stash it in an arrraylist for use on save.
private void toolStripButton1_Click (object sender, EventArgs e)
{
var currentclient = (Client) clientBindingSource.Current;
clientstodelete.Add (currentclient);
clientBindingSource.RemoveCurrent ();
}
I didn't need to create a new button, I just needed to have the this.clientBindingNavigator.DeleteItem not tied to a button. Because the DeleteItem creates a click event under the hood that calls the BindingSource.RemoveCurrent(). I might change the button back to the default one created but for illustration wanted everyone to see what was happening.
I agree it seemed odd to have to delete the record from the table directly if I had just deleted using RemoveCurrent(). But it is what it is... This took care of the record in the datagridview and the datasource in one clean sweep.
Here is how I solved the problem:
t_StaffDaysOff sdo = (t_StaffDaysOff)t_StaffDaysOffbindingSource.Current;
t_StaffDaysOffbindingSource.RemoveCurrent();
t_StaffDaysOffbindingSource.EndEdit();
db.t_StaffDaysOff.Remove(sdo);
db.SaveChanges();

C# WinForms cannot set focus

Im currently facing the problem that when i try to set focus on some control (textBox), nothing happens, maybe i just overlooked something.(somewhere i found that focus is "low-level" method and that select() should be used instead, however, it doesnt work as well)
From form Login, i launch new instance of EncryptPSW form
private void openToolStripMenuItem_Click(object sender, EventArgs e)
{
EncryptPSW ePSW = new EncryptPSW();
ePSW.setOsLog(false, this);
ePSW.ShowDialog();
}
On Button(which is located on EncryptPSW form ) click event i call fill method
public void fill()
{
if (textBoxPSW.Text.Length == 8)//psw has to be 8 chars long
{
if (save)//determinating whether save or fetch of data should be done
{ login.launchSave(textBoxPSW.Text,this); }
else { login.launchOpen(textBoxPSW.Text,this); }
}
else { MessageBox.Show("The password must contain 8 characters");}
}
Which launches either save or open method from Login (my problem is just with open, since during save i dont need to do anything with Focus)
public void launchOpen(string psw,EncryptPSW ePSW)
{
ePSW.Close();
Encryptor.DecryptFile("loggin.bin", psw, this); //decrypting data and setting textBoxes Text property into the fetched ones
setFocus();
}
After all the work is done, setFocus() should be called in order to set focus and other properties.
public void setFocus()
{
textBoxDatabase.Focus();
textBoxDatabase.SelectionStart = textBoxDatabase.TextLength - 1;
textBoxDatabase.SelectionLength = 0;
}
I tried so many different ways, like:
Calling setFocus() from within EncryptPSW_FormClosed
Calling whole open process after the EncryptPSW is closed (from within EncryptPSW_FormClosed)
and many more, however i dont remember it all.
In the case of Form_Closed the weird thing is, that when i tried to show a message box from there instead of setting focus (just to see where the problem might be), it's showed before the EncryptPSW form is closed.
My only guess about this is that the instance of EncryptPSW is somehow blocking Login form and it's controls
I hoped i described my problem well enough and that it makes at least a bit of sense ;]
Thanks in advance,
Regards,
Releis
Since the textbox is in the login form, and you are opening the EcryptPWS from it as a dialog (child), your login form will not be able to set focus to anything. You will need to set focus after it is closed. You can do this:
private void openToolStripMenuItem_Click(object sender, EventArgs e)
{
using(EncryptPSW ePSW = new EncryptPSW())
{
ePSW.setOsLog(false, this);
if (ePSW.ShowDialog() == DialogResult.OK)
{
textBoxDatabase.Focus();
}
}
}
public void launchOpen(string psw,EncryptPSW ePSW)
{
ePSW.DialogResult = DialogResult.OK;
ePSW.Close();
Encryptor.DecryptFile("loggin.bin", psw, this); //decrypting data and setting textBoxes Text property into the fetched ones
}
OK this maybe the ugliest thing I saw round this but.
using
public void setFocus()
{
textBoxDatabase.Focus();
textBoxDatabase.SelectionStart = textBoxDatabase.TextLength - 1;
textBoxDatabase.SelectionLength = 0;
}
Change your code at
public void launchOpen(string psw,EncryptPSW ePSW)
{
ePSW.Close();
Encryptor.DecryptFile("loggin.bin", psw, this); //decrypting data and setting textBoxes Text property into the fetched ones
setFocus();
}
to
delegate void settingfocus();
public void launchOpen(string psw,EncryptPSW ePSW)
{
ePSW.Close();
Encryptor.DecryptFile("loggin.bin", psw, this); //decrypting data and setting textBoxes Text property into the fetched ones
settingfocus sf = new settingfocus(setFocus);
this.BeginInvoke(sf);
}
This worked for me
(Sorry for apparently thinking insert "this" before procedure, and change line x to this was legable)

Method executing several times even though it is called once

I am doing a project which includes dynamic controls creation and removal from the WinForm,
So I decided to that part on a small test project.
Test project has two files Form1.cs and NewControls.cs. This program creates additional buttons whenever user clicks an Add button already on the form.And removes the newly created button when it is clicked (self removal button). Also after removal of button other button's Name, Text and their position are changed according to a local variable (controlIndex).
Form1.cs
public partial class Form1 : Form
{
static List<NewControl> newControlsList = new List<NewControl>();
public Form1()
{
InitializeComponent();
}
private void Add_Click(object sender, EventArgs e)
{
newControlsList.Add(new NewControl(newControlsList.Count));
}
public static void RemoveButton(object sender, EventArgs e)
{
NewControl tempNewControl = (NewControl)(sender as Button).Tag;
tempNewControl.RemoveControl();
newControlsList.Remove(tempNewControl);
MessageBox.Show("Removed!");
foreach (NewControl tempcontrol in newControlsList)
{
tempcontrol.controlIndex = newControlsList.IndexOf(tempcontrol);
tempcontrol.PlaceControl();
}
}
}
NewControl.cs
class NewControl
{
public int controlIndex = 0;
Button newButton = new Button();
public NewControl(int index)
{
controlIndex = index;
PlaceControl();
}
public void RemoveControl()
{
newButton.Dispose();
Form1.ActiveForm.Controls.Remove(newButton);
}
public void PlaceControl()
{
newButton.Tag = this;
newButton.Name = "btn" + controlIndex.ToString("D2");
newButton.Text = "btn" + controlIndex.ToString("D2");
newButton.Size = new Size(100, 20);
newButton.Left = controlIndex * 100;
Form1.ActiveForm.Controls.Add(newButton);
newButton.Click += new EventHandler(Form1.RemoveButton);
}
}
Program works nearly as expected. Problem is the MessageBox which I used in form1.cs in RemoveButton() fires many time (as opposed to just one time), which implies whole method being executed several times. Actually I pasted that MessageBox for debugging (sort of).
Since I cannot debug the application as when "Form1.ActiveForm.Controls.Add(newButton);" statement is executed, debugger Throws NullReferenceException, as there is not an active form while debugging.
I know that's like a bonus question but I thought to just put it there. I am a beginner and can't see the way through both the problems. 1st problem is really important for my original project as it will cause problem when many controls are added.
I think it is because you call PlaceControl from Form1.cs AND in the constructor of the NewControl class, Because you say newButton.Click += new EventHandler(Form1.RemoveButton);.
You are adding EventHandlers, so there can be more of them.
So when you call placeControl multiple times, you've got multiple event handlers, i think.
Probably the EventHandler hasn't been removed by RemoveButton. (I've been working in java most recently so my terms might be a little off for C#.) Suggestion: set control visibility to true when you want it and false otherwise rather than adding and removing.
Everytime a button is removed you go over your existing list of controls, and you call "PlaceControl", which attaches yet another handler.
foreach (NewControl tempcontrol in newControlsList)
{
tempcontrol.controlIndex = newControlsList.IndexOf(tempcontrol);
tempcontrol.PlaceControl();
}
Remove the above code block from RemoveButton, and you will see that your dynamically added buttons will each only trigger the event once.
In your RemoveButton event you loop on each button and call again PlaceControl.
The only reason is to reposition the remainder controls.
I think it's better a call to a separate method that do only this work.
public void RepositionControl()
{
newButton.Left = controlIndex * 100;
}
this will prevent to mess with event handlers added more than one time

How do I safely populate with data and Refresh() a DataGridView in a multi-threaded application?

My app has a DataGridView object and a List of type MousePos. MousePos is a custom class that holds mouse X,Y coordinates (of type "Point") and a running count of this position. I have a thread (System.Timers.Timer) that raises an event once every second, checks the mouse position, adds and/or updates the count of the mouse position on this List.
I would like to have a similar running thread (again, I think System.Timers.Timer is a good choice) which would again raise an event once a second to automatically Refresh() the DataGridView so that the user can see the data on the screen update. (like TaskManager does.)
Unfortunately, calling the DataGridView.Refresh() method results in VS2005 stopping execution and noting that I've run into a cross-threading situation.
If I'm understanding correctly, I have 3 threads now:
Primary UI thread
MousePos List thread (Timer)
DataGridView Refresh thread (Timer)
To see if I could Refresh() the DataGridView on the primary thread, I added a button to the form which called DataGridView.Refresh(), but this (strangely) didn't do anything. I found a topic which seemed to indicate that if I set DataGridView.DataSource = null and back to my List, that it would refresh the datagrid. And indeed this worked, but only thru the button (which gets handled on the primary thread.)
So this question has turned into a two-parter:
Is setting DataGridView.DataSource to null and back to my List an acceptable way to refresh the datagrid? (It seems inefficient to me...)
How do I safely do this in a multi-threaded environment?
Here's the code I've written so far (C#/.Net 2.0)
public partial class Form1 : Form
{
private static List<MousePos> mousePositionList = new List<MousePos>();
private static System.Timers.Timer mouseCheck = new System.Timers.Timer(1000);
private static System.Timers.Timer refreshWindow = new System.Timers.Timer(1000);
public Form1()
{
InitializeComponent();
mousePositionList.Add(new MousePos()); // ANSWER! Must have at least 1 entry before binding to DataSource
dataGridView1.DataSource = mousePositionList;
mouseCheck.Elapsed += new System.Timers.ElapsedEventHandler(mouseCheck_Elapsed);
mouseCheck.Start();
refreshWindow.Elapsed += new System.Timers.ElapsedEventHandler(refreshWindow_Elapsed);
refreshWindow.Start();
}
public void mouseCheck_Elapsed(object source, EventArgs e)
{
Point mPnt = Control.MousePosition;
MousePos mPos = mousePositionList.Find(ByPoint(mPnt));
if (mPos == null) { mousePositionList.Add(new MousePos(mPnt)); }
else { mPos.Count++; }
}
public void refreshWindow_Elapsed(object source, EventArgs e)
{
//dataGridView1.DataSource = null; // Old way
//dataGridView1.DataSource = mousePositionList; // Old way
dataGridView1.Invalidate(); // <= ANSWER!!
}
private static Predicate<MousePos> ByPoint(Point pnt)
{
return delegate(MousePos mPos) { return (mPos.Pnt == pnt); };
}
}
public class MousePos
{
private Point position = new Point();
private int count = 1;
public Point Pnt { get { return position; } }
public int X { get { return position.X; } set { position.X = value; } }
public int Y { get { return position.Y; } set { position.Y = value; } }
public int Count { get { return count; } set { count = value; } }
public MousePos() { }
public MousePos(Point mouse) { position = mouse; }
}
You have to update the grid on the main UI thread, like all the other controls. See control.Invoke or Control.BeginInvoke.
UPDATE! -- I partially figured out the answer to part #1 in the book "Pro .NET 2.0 Windows Forms and Customer Controls in C#"
I had originally thought that Refresh() wasn't doing anything and that I needed to call the Invalidate() method, to tell Windows to repaint my control at it's leisure. (which is usually right away, but if you need a guarantee to repaint it now, then follow up with an immediate call to the Update() method.)
dataGridView1.Invalidate();
But, it turns out that the Refresh() method is merely an alias for:
dataGridView1.Invalidate(true);
dataGridView1.Update(); // <== forces immediate redraw
The only glitch I found with this was that if there was no data in the dataGridView, no amount of invalidating would refresh the control. I had to reassign the datasource. Then it worked fine after that. But only for the amount of rows (or items in my list) -- If new items were added, the dataGridView would be unaware that there were more rows to display.
So it seems that when binding a source of data (List or Table) to the Datasource, the dataGridView counts the items (rows) and then sets this internally and never checks to see if there are new rows/items or rows/items deleted. This is why re-binding the datasource repeatedly was working before.
Now to figure out how to update the number of rows to display in dataGridView without having to re-bind the datasource... fun, fun, fun! :-)
After doing some digging, I think I have my answer to part #2 of my question (aka. safe Multi-threading):
Rather than using System.Timers.Timer, I found that I should be using System.Windows.Forms.Timer instead.
The event occurs such that the method that is used in the Callback automatically happens on the primary thread. No cross-threading issues!
The declaration looks like this:
private static System.Windows.Forms.Timer refreshWindow2;
refreshWindow2 = new Timer();
refreshWindow2.Interval = 1000;
refreshWindow2.Tick += new EventHandler(refreshWindow2_Tick);
refreshWindow2.Start();
And the method is like this:
private void refreshWindow2_Tick(object sender, EventArgs e)
{
dataGridView1.Invalidate();
}
Looks like you have your answer right there!
Just in cawse you're curious about how to do cross thread calls back to ui:
All controls have a Invoke() method (or BEginInvoke()- in case you want to do things asynchronously), this is used to call any method on the control within the context of the main UI thread.
So, if you were going to call your datagridview from another thread you would need to do the following:
public void refreshWindow_Elapsed(object source, EventArgs e)
{
// we use anonymous delgate here as it saves us declaring a named delegate in our class
// however, as c# type inference sometimes need a bit of 'help' we need to cast it
// to an instance of MethodInvoker
dataGridView1.Invoke((MethodInvoker)delegate() { dataGridView1.Invalidate(); });
}

Categories