CollectionViewSource.View not refreshing at end points - c#

I'm running into a problem with my CollectionViewSource.
I'm stopping the user to go BEYOND the the items within the CollectionViewSource to prevent my code from falling over.
I have this:
private CollectionViewSource _source = new CollectionViewSource();
private List<Items> _itemLists;
private MyDbContext _context;
Constructor()
{
_itemLists = _context.Items.ToList();
_source.Source = _itemLists;
_source.View.CurrentChanged += View_CurrentChanged;
}
private void View_CurrentChanged(object sender, EventArgs e)
{
if (_source.View.IsCurrentBeforeFirst)
{
_source.View.MoveCurrentToFirst();
_source.View.Refresh();
return;
}
if (_source.View.IsCurrentAfterLast)
{
_source.View.MoveCurrentToLast();
_source.View.Refresh();
return;
}
// ... Do some other work here ...
}
I had to put in a return statement in each if blocks to prevent the View.CurrentItem from being NULL.
This works great from preventing a Null exception being thrown, but the strange behavior is that when I try to call View.MoveCurrentToNext() after the user tries to go beyond the first record (IsCurrentViewBeforeFirst), nothing happens. You have to call it again in order for the View to be updated to the next record after the first one.
Inversely, the same behavior goes for the IsCurrentViewAfterLast. You have to call View.MoveCurrentToPrevious() twice before the View shows the previous record after the last one when the user tries to go beyond the last record.
So then how can this be fixed so I don't need to call the methods twice?

You can prevent event bubbling in WPF like below:
e.Handled = true;
I think your CollectionViewSource object inherited from an IEnumerable object. Which has a member as Index you can hide your object in a session or any static container (for example Application.Current.Resources in wpf) so you can update just your index while user click next or before buttons.
If your object doesnt inherited from IEnumerable i suggest to this also
Cheer codding

Related

C# subclassing BindingList, ListChanged fires outside of class, but not inside class

I'm trying to wrap my head around how BindingList works in a dummy project. The eventual goal is [Microsoft.VisualBasic.PowerPacks.DataRepeater]->[BindingList of POCOs that are backed by Dapper/SQL Server Compact and automagically do CRUD on ListChanged] (as opposed to [DataRepeater]->[DataTable]; please DO leave feedback on validity of this approach in the comments).
My rationale for Add()'ing records in the dummycollection constructor was that it should SELECT existing records from the db at the time of its birth.
The specific question of the moment is: Why does Add()'ing behave differently on the inside versus the outside? My expectation is that the ListChanged event should fire in both cases, but it doesn't. I'm sure I'm missing something simple.
Outside:
private void Form1_Shown(object sender, EventArgs e)
{
dummies = new dummycollection();
dummyBindingSource.DataSource = dummies;
dummies.Add(new dummy() { dummyid=0, dummytext="outside" }); //outside, gets message
}
Inside:
class dummycollection : BindingList<dummy>
{
public dummycollection() // ctor
{
ListChanged += HandleChange;
Items.Add(new dummy() { dummyid=0, dummytext="ctor0" }); //inside, no message
}
private void HandleChange(object sender, ListChangedEventArgs e)
{
System.Diagnostics.Debug.Print(e.ListChangedType.ToString());
}
}
Furthermore, the "insider" record behaves strangely compared to the "outsider" record. When I put a DataGridView on my form for troubleshooting, and edit dummytext in the repeater and grid, visible updates between the two are sporadic or unpredictable when clicking around the various controls. The "outsider" record visibly updates more consistently.

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)

Events for adding/removing items in a listbox c#.NET

I have a listbox control that has items dynamically added to and manually removed from (due to 'remove item' button). When the number of items is changed, I would like to update other parts of the user interface - namely a caption that says 'You must choose some files.' and an item count caption.
How can an event handler or effectively an event handler be added to fire when the number of items is changed - e.g. an ItemAdded or ItemRemoved or ItemsChanged
Note: This is nothing to do with the user selecting items in the listbox.
Thanks very much!
You can try using a BindingList<> as your DataSource, and then you act on that list instead of your ListBox-- it will get the updates automatically from the BindingList.
The BindingList has a ListChanged event.
The ListChanged event has a ListChangedEventArgs that includes a ListChangedType enumerator:
BindingList<string> list = new BindingList<string>();
list.ListChanged += new ListChangedEventHandler(list_ListChanged);
void list_ListChanged(object sender, ListChangedEventArgs e) {
switch (e.ListChangedType){
case ListChangedType.ItemAdded:
break;
case ListChangedType.ItemChanged:
break;
case ListChangedType.ItemDeleted:
break;
case ListChangedType.ItemMoved:
break;
// some more minor ones, etc.
}
}
In the code for the "Remove Item" button, also update the other parts of the UI. This doesn't have to violate coding principles; you can do something like this:
void UpdatePartsOfTheUI() {
// Do something here
if(myListBox.Items.Count == 0) {
myLabel.Text = "You must choose some files!";
} else {
myLabel.Text = String.Empty;
}
}
/* ... */
void myButton_Click(object sender, EventArgs e) {
if(myListBox.SelectedIndex > -1) {
// Remove the item
myListBox.Items.RemoveAt(myListBox.SelectedIndex);
// Update the UI
UpdatePartsOfTheUI();
}
}
This works if you don't have many buttons that change the ListBox. If you do, just wrap the ListBox's Items.Add/Items.Insert/Items.Remove in methods that include your other code and call those from your button handlers instead.
Create a method to which you can do what you want with the ListBox Item.
for example:
my program receives data feeds from a server upon request or request for stream.
In my winform1.cs my control for adding data to a list box is as followed.
public void AddData(string data)
{
if (this.ResponseData.InvokeRequired)
BeginInvoke(new AddDataDelegate(AddData), new object[] { data });
else
{
this.ResponseData.Items.Insert(0, data);
DataDistro();
}
}
DataDistro Is what I called My Method for doing work with the new data. Also Note by inserting at index value 0, the new item will Always be on top.
If you are using winForm this is a lot easier, Also, Because the adding of an item is handled by a delegate, the main thread is still open. If your not using a method that is adding all the data to the listbox this will not work. And using the bindingsource method mentioned above would be the next best thing.
here is an example of my DataDistro method: My response strings look like this: [Q],ATQuoteLastPrice,value
[B],datetime,open,high,low,close,volume
private void DataDistro()
{
string data = ListBox.Items[0].ToString();
string[] split = data.Split(new string[] {","}, stringsplitoptions.None);
if(spit[0] == "[Q]")
{
//do some work
}
if(split[0] == "[B]")
{
//Do some work
}
}
In your case you would call your method at end of the remove item button click. I would also suggest to make a delegate, or backgroundWorker if the work is extensive. As Calling from a button click Event will be handled by UI Thread.
Everytime the AddData method is called upon, the DataDistro method is also called after data is added.

What happens when you attempt to access a winforms TreeView by keyword and that Keyword is not present?

I'm writing an application where I fill out a TreeView with the schema of a database. I do so by iterating over each table name and type in GetSchema. Then, depending on the DataType and name, I select the parent Node into which I want to add a new item. Sometimes that item does not exist in the treenode (depending on user settings certain tables may or may not have been added as nodes of the treeview), which is fine, in that case I want either:
A) An exception to be thrown so I KNOW that it failed to find the node as asked for. Or B) null to be returned for the failed accessor.
A (highly modified) snippet of my code:
TreeNode parent = null;
if( tableName.StartsWith("prefix") )
{
parent = tablesNode.Nodes["Node Name which might not exist"];
}
if (parent == null && IgnorePrefixedTables)
{
continue;
}
else if (parent == null)
{
throw Exception();
}
....<More Code For Filling Out that node>...
The problem is, that when I step through this code (or rather, the real code) when I reach tablesNode.Nodes["Node Name which might not exist"] for a node name that doesn't exist I am unable to catch the exception because none is thrown. If I step into or over that line of code the whole method returns me up to the highest level (my form immediately gets shown and the UI is partially complete). What's up with that?
[EDIT]
Here's a VERY simplified version of my problem:
namespace TestZone
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
treeView1.Nodes.Add("Hello", "Hello");
var x = treeView1.Nodes["Hello"];
x.Nodes.Add("World-PL", "Swiat");
x.Nodes.Add("World-EN", "World");
var y = treeView1.Nodes["World-EN"];
MessageBox.Show(y.Text);
y = treeView1.Nodes["World-SP"];
MessageBox.Show(y.Text);
y = treeView1.Nodes["World-PL"];
MessageBox.Show(y.Text);
}
}
}
The code relies on textBox1 being on Form1. (P.S. PL is Polish). Apparently treeView1 can't find World-EN either which makes me think I'm really not understanding how treeView works. The first MessageBox never gets shown and breakpoints on y = treeView1.Nodes["World-SP"]; fail to break (since that line of code never gets called).
Avoid using exceptions to control program flow. Use the TreeViewCollection.IndexOfKey() method.
There's a strange bug on a 64-bit operating system when an exception is raised in the form's OnLoad() method or Load event and a debugger is attached. It is swallowed without notification. Sounds like a match. Workaround is to set the Platform target to x86.

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