My friend and I have been working on a game and we have a trace listener set up, here's some of the code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
namespace GuiGame {
/// <summary>
/// A type of Trace Listener that sends its output to a ListBox.
/// </summary>
public class ListBoxTraceListener : TraceListener {
private ListBox listBox; // A reference to the listbox that we're writing to.
private string stringToAddToListBox = "";
private const char NEW_LINE = '\n';
/// <summary>
/// Parameterless constructor.
/// Do not want the generic default constructor to be used
/// as there is no way to set the ListBoxTraceListener's data.
/// This replaces the compiler's generic default constructor.
/// Pre: none
/// Post: ALWAYS throws an ArgumentException.
/// </summary>
/// <remarks>NOT TO BE USED!</remarks>
public ListBoxTraceListener() {
throw new ArgumentException("Parameterless constructor invalid.");
} // end ListBoxTraceListener constructor
/// <summary>
/// Constructor with initialising parameters.
/// Pre: the existence of a ListBox on a GUI form.
/// Post: initialised object.
/// </summary>
/// <param name="listBox">The ListBox that we're writing to.</param>
public ListBoxTraceListener(ListBox listBox) {
this.listBox = listBox;
}
/// <summary>
/// Automatically collects the outputs from all Trace.WriteLine statements.
/// Pre: none.
/// Post: the string s is displayed in the listBox.
/// </summary>
/// <param name="s"></param>
public override void WriteLine(string s) {
Write(s + NEW_LINE);
} //end WriteLine
/// <summary>
/// Automatically collects the outputs from all Trace.Write statements.
/// Pre: none.
/// Post: the string s is displayed in the listBox, once we receive a NEW_LINE.
/// </summary>
/// <param name="s"></param>
public override void Write(string s) {
stringToAddToListBox += s;
// If we have one or more complete lines
if (stringToAddToListBox.Contains (NEW_LINE)) {
// Split the string into multiple lines.
// If NEW_LINE is found at the beginning or end of the string,
// then the corresponding array element contains an empty string.
string[] lines = stringToAddToListBox.Split(NEW_LINE);
// Add all the lines to the listbox, except for the last one.
// When stringToAddToListBox has a new-line at the end,
// the last element in lines[] will be an empty string.
int highestLineNumber = lines.Length - 1;
for (int i = 0; i < highestLineNumber; i++) {
AddToListBox(lines[i]);
}
// Reset stringToAddToListBox to what remains. (May be an empty string).
stringToAddToListBox = lines[highestLineNumber];
}
} // end Write
/// <summary>
/// Adds a complete output-line to the ListBox.
/// Pre: none.
/// Post: the string listBoxLine is displayed in the listBox .
/// </summary>
/// <param name="listBoxLine"></param>
private void AddToListBox(string listBoxLine) {
Debug.Assert(listBox != null, "listBox != null");
listBox.Items.Add(listBoxLine);
} // end AddToListBox
}
}
At this stage we are just trying to use the trace listener to output some text on in the ListBox so we know it is working, so we have an event handler setup:
private void RollDiceButton_Click(object sender, EventArgs e)
{
}
We haven't been able to get any output from the trace listener. The Add method as the trace listener is not set up for that. Can anyone provide some suggestions please? I think maybe we are doing something really stupid and obvious that we have missed.
The most probable cause of your issue is that your application is single threaded (as most simple Windows applications are). This means that although you are sending messages to the listview to append a new element to the list, the message is not yet handled just because your original handler hasn't returned yet (the RollDiceButton_Click).
To work this around, you should force the list to refresh itself from within the current handler:
private void AddToListBox(string listBoxLine) {
Debug.Assert(listBox != null, "listBox != null");
listBox.Items.Add(listBoxLine);
// this would help?
listBox.Refresh();
} // end
If this doesn't help, try temporarily switching to unconditional processing of all pending events with
private void AddToListBox(string listBoxLine) {
Debug.Assert(listBox != null, "listBox != null");
listBox.Items.Add(listBoxLine);
// this would help?
Application.DoEvents();
} // end
and report back.
Related
I want to pinpoint I'm totally new to C# and I'm just testing around to get a grasp of this language.
Want I want to achieve is to just console-print in a secondary window a couple of values I've set in the MainWindow.
This is a function contained in the MainWindow class, triggered by a button click.
private void ValidationExecuted(object sender, ExecutedRoutedEventArgs eventArgs)
{
// If the validation was successful, let's open a new window.
GeneratorWindow generatorWindow = new GeneratorWindow();
generatorWindow.TextBlockName1.Text = this.tbPoints.Text;
generatorWindow.TextBlockName2.Text = this.tbPDC.Text;
int.TryParse(this.tbPoints.Text, out int numberOfPoints);
int.TryParse(this.tbPDC.Text, out int pdc);
// Those lines correctly print the values I've inserted in the TextBoxes.
Console.WriteLine(numberOfPoints);
Console.WriteLine(pdc);
generatorWindow.NumberOfPoints = numberOfPoints;
generatorWindow.MainPDC = pdc;
generatorWindow.Show();
// Resetting the UI.
this.validator = new Validator();
this.grid.DataContext = this.validator;
eventArgs.Handled = true;
}
Now my secondary window:
public partial class GeneratorWindow : Window
{
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:ABB_Rapid_Generator.GeneratorWindow" /> class.
/// </summary>
public GeneratorWindow()
{
this.InitializeComponent();
// Those lines just print a pair of 0.
Console.WriteLine(this.NumberOfPoints);
Console.WriteLine(this.MainPDC);
}
/// <summary>
/// Gets or sets the number of points.
/// </summary>
public int NumberOfPoints { private get; set; }
/// <summary>
/// Gets or sets the main PDC.
/// </summary>
public int MainPDC { private get; set; }
}
As you can see in the code comments, the Console.WriteLine() contained in the main class are correctly working. Moreover I can assign my custom values to the TextBlocks contained in the other class. On the contrary, the Console.WriteLine() lines in the secondary class are just outputting a couple of zeros.
What have I been missing?
The problem is that in GeneratorWindow you are writing to the console in the constructor method, so the values are being output before you are changing them.
The only way you can really get that output to work would be to pass the values as parameters of the constructor and set them (in the constructor) before you do the console output. Though there doesn't seem any logical reason to go down that path.
For example:
public GeneratorWindow(int numberOfPoints, int mainPdc)
{
this.InitializeComponent();
this.NumberOfPoints = numberOfPoints;
this.MainPDC = mainPdc;
Console.WriteLine(this.NumberOfPoints);
Console.WriteLine(this.MainPDC);
}
Alternatively, if you want to see the values after you set them, then you will need to move your console outputs to another function that you call after you have set the values.
For example, add this function to GeneratorWindow:
public void OutputValues()
{
Console.WriteLine(this.NumberOfPoints);
Console.WriteLine(this.MainPDC);
}
Which you can then call after you have set the values in your other class:
GeneratorWindow generatorWindow = new GeneratorWindow();
generatorWindow.NumberOfPoints = numberOfPoints;
generatorWindow.MainPDC = pdc;
generatorWindow.OutputValues();
you can add a parameter-ize constructor to do so
public partial class GeneratorWindow : Window
{
//Private members
int m_numberOfPoints;
int m_mainPDC;
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:ABB_Rapid_Generator.GeneratorWindow" /> class.
/// </summary>
public GeneratorWindow(int mainPDC,int numberOfPoints)
{
this.InitializeComponent();
this.m_mainPDC = mainPDC;
this.m_numberOfPoints = numberOfPoints;
}
/// <summary>
/// Gets or sets the number of points.
/// </summary>
public int NumberOfPoints
{
get{ return m_numberOfPoints; }
set{ m_numberOfPoints = values; }
}
/// <summary>
/// Gets or sets the main PDC.
/// </summary>
public int MainPDC
{
get{ return m_mainPDC; }
set{ m_mainPDC= values; }
}
public void Print()
{
Console.WriteLine(this.NumberOfPoints);
Console.WriteLine(this.MainPDC);
}
}
also this is a constructor so it will be just called at
GeneratorWindow generatorWindow = new GeneratorWindow();//here
Change the secondary window call to
generatorWindow = new GeneratorWindow(pdc,numberOfPoints);
generatorWindow.Print();
Also, your code is not done in a good way IMO, why set values like this?
generatorWindow.TextBlockName1.Text = this.tbPoints.Text;
generatorWindow.TextBlockName2.Text = this.tbPDC.Text;
If you have private variables set just as above sample you can perform all converting, printing and , receiving console output in same class.you'll need to just call the constructor and print method.
Answer above is correct, but there is an alternative.
You can use setter methods like setNumberOfPoints, setMainPDC and print co console after setting the value. So in ValidationExecuted you call for a function to set variable, and in that function after setting variable you print it to console. But don't forget to remove printing to console from constructor
I've written a DLL that does a bunch of stuff. One of the things it does is to search through a lot of incoming messages for a specific message, clean up the message, and raise an event when the message is found, and pass the data via the EventArgs.
Currently the DLL is working but the routine that searches for the message is slowing everything down and making the system slow to respond because there is so much data to go through.
I would like to move this searching to it's own thread, and once the message is found have it raise an event on the main thread and pass the message data to the main thread. I know how to make a thread to do the work, but I do not know how to make the thread raise the event on the main thread and pass the data to it. Could someone tell me how to do this, or point to an example of it?
I've tried creating a delegate and triggering an event, which works but when I try to pass the data I get a cross thread exception.
Some details that may be important. There most likely won't be a GUI at all and will probably remain a console application. Messages will always be coming in and the DLL has to continuously search them. The message the DLL is looking for may come as often as 1 couple times a second or it may be days between the messages.
Update for a real simple project illustrating what I would like to do. This code is rough cause I threw it together to test this out.
If you follow the code you will see that everything runs fine until the line in the CollectData function where:
StatusUpdated(this, null);
is called. At this point it causes the cross thread exception because of the UI thread and the data Collection thread. I know I can fix this by using an invoke on the UI thread, but as mentioned above this will most likely not be used with any sort of GUI (but it should be able to handle it). So the cross thread exception needs to be fixed in the BuildResultsManager class, I think in the function BRMgr_StatusUpdated. How do I get the data (in this case the value of currentVal) to the UI thread without changing any code in the MainWindow class.
You can run this by creating a solution with 2 projects, 1 for the first 2 files as a dll. The second as a wpf project, referencing the dll, and put a textbox named status on the form.
Main Class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace BuildResultsManager
{
/// <summary>
/// Delegate to notify when data has changed
/// </summary>
/// <param name="sender">unused</param>
/// <param name="e">unused</param>
public delegate void StatusUpdatedEventHandler(object sender, EventArgs e);
/// <summary>
/// Connects to the PLC via PVI and reads in all the build results data, conditions it, updates values and reads/store into the database
/// </summary>
public class BuildResultsManager
{
#region events
// Change in The Build Results Manger Status
public event StatusUpdatedEventHandler StatusUpdated;
#endregion
#region Local variables
private Thread collectionThread;
private string statusMessage;
DataCollector dataCollection;
#endregion
#region Properties
/// <summary>
/// Current Status of the Build Results manager
/// </summary>
public String StatusMessage
{
get
{
return statusMessage;
}
private set
{
statusMessage = value;
if (StatusUpdated != null)
StatusUpdated(this, null);
}
}
#endregion
#region Constructors
/// <summary>
/// Default constructor
/// </summary>
public BuildResultsManager()
{
StatusMessage = "successfully initialized";
// start the thread that will collect all the data
dataCollection = new DataCollector();
dataCollection.StatusUpdated += new DCStatusUpdatedEventHandler(BRMgr_StatusUpdated);
collectionThread = new Thread(new ThreadStart(dataCollection.CollectData));
collectionThread.Start();
}
/// <summary>
/// EVent to handle updated status text
/// </summary>
/// <param name="sender">unused</param>
/// <param name="e">unused</param>
void BRMgr_StatusUpdated(object sender, EventArgs e)
{
StatusMessage = dataCollection.currentVal.ToString();
}
#endregion
}
}
The class that will be doing all of the thread work:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Windows.Threading;
using System.Threading;
namespace BuildResultsManager
{
/// <summary>
/// Delegate to notify when data has changed
/// </summary>
/// <param name="sender">unused</param>
/// <param name="e">unused</param>
public delegate void DCStatusUpdatedEventHandler(object sender, EventArgs e);
/// <summary>
/// Handles all of the data collection and conditioning
/// </summary>
class DataCollector
{
#region events
// Change in The Build Results Manger Status
public event DCStatusUpdatedEventHandler StatusUpdated;
#endregion
private const long updateInterval = 1000;
private Stopwatch updateTimer;
public int currentVal;
#region local variables
private bool shouldStop;
#endregion
/// <summary>
/// Default Constructor
/// </summary>
public DataCollector()
{
shouldStop = false;
updateTimer = new Stopwatch();
updateTimer.Start();
}
/// <summary>
/// Main task that listens for new data and collects it when it's available
/// </summary>
public void CollectData()
{
currentVal = 5;
while (!shouldStop && currentVal < 10)
{
if(updateTimer.ElapsedMilliseconds > updateInterval)
{
currentVal++;
if (StatusUpdated != null)
{
StatusUpdated(this, null);
}
//reset the timer
updateTimer.Restart();
}
}
}
/// <summary>
/// Asks the thread to stop
/// </summary>
public void RequestStop()
{
shouldStop = true;
}
}
}
Code behind for the wpf project:
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
#region Local variables
private string BRMgrStatus;
private BuildResultsManager.BuildResultsManager BRMgr;
#endregion
public MainWindow()
{
InitializeComponent();
// create an instance of the build manager
BRMgr = new BuildResultsManager.BuildResultsManager();
if(BRMgr != null)
{
status.Text = BRMgr.StatusMessage;
BRMgr.StatusUpdated += new StatusUpdatedEventHandler(BRMgr_StatusUpdated);
}
}
/// <summary>
/// EVent to handle updated status text
/// </summary>
/// <param name="sender">unused</param>
/// <param name="e">unused</param>
void BRMgr_StatusUpdated(object sender, EventArgs e)
{
BuildResultsManager.BuildResultsManager brm;
brm = (BuildResultsManager.BuildResultsManager)sender;
status.Text = brm.StatusMessage;
}
}
If you don't use a GUI there's no need to execute things on the same thread, just protect non-thread-safe resources with a locking mechanism (lock, mutex, etc) to avoid concurrent access and you're good.
I am writing an updater program on in C# using Visual Studio 2008 on Windows 7. I would like the user to insert a USB thumb drive and if the program finds the drive and updates on the drive then it automatically copies them over. I desire to check only once at startup and then execute a program that is unaware of the updates (updates really need to happen with the program shutdown).
My issue is that the update program is being run before the thumb drive is being mounted so the computer detects no thumb drive and no updates and moves on prematurely. I want to have everything running as fast as possible but I need to force any thumb drives to mount before detection. Everything must be automatic with no input from the user.
Is this possible in c#?
Edit with more detail:
I currently run a batch file at startup (actually as the Windows 7 shell, but I'm not sure that makes a difference). The batch file runs the update check then the actual program. If the user had the USB drive stuck in at boot then I would like the updater to look at the drive and copy over any new files.
The current coded looks like:
DriveInfo[] ListDrives = DriveInfo.GetDrives();
foreach (DriveInfo Drive in ListDrives)
{
if(Drive.DriveType == DriveType.Removable)
{
// double check it's valid and copy over stuff
}
}
but it currently finds no drives at boot. If I run it later then everything is fine. I am assuming that since I run the updater so early it just hasn't had a chance to mount, but I don't just want to wait N seconds if I don't have to because under normal circumstances that's just dead time.
If I can do this check easy up from it is much simpler than having to continually monitor for an event and then shut everything down and do an update.
I would suggest a solution like the following one:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
/// <summary>
/// Represents our program class which contains the entry point of our application.
/// </summary>
public class Program
{
/// <summary>
/// Represents the entry point of our application.
/// </summary>
/// <param name="args">Possibly spcified command line arguments.</param>
public static void Main(string[] args)
{
RemovableDriveWatcher rdw = new RemovableDriveWatcher(); // Create a new instance of the RemoveableDriveWatcher class.
rdw.NewDriveFound += NewDriveFound; // Connect to the "NewDriveFound" event.
rdw.DriveRemoved += DriveRemoved; // Connect to the "DriveRemoved" event.
rdw.Start(); // Start watching.
// Do something here...
Console.ReadLine();
rdw.Stop(); // Stop watching.
}
/// <summary>
/// Is executed when a new drive has been found.
/// </summary>
/// <param name="sender">The sender of this event.</param>
/// <param name="e">The event arguments containing the changed drive.</param>
private static void NewDriveFound(object sender, RemovableDriveWatcherEventArgs e)
{
Console.WriteLine(string.Format("Found a new drive, the name is: {0}", e.ChangedDrive.Name));
}
/// <summary>
/// Is executed when a drive has been removed.
/// </summary>
/// <param name="sender">The sender of this event.</param>
/// <param name="e">The event arguments containing the changed drive.</param>
private static void DriveRemoved(object sender, RemovableDriveWatcherEventArgs e)
{
Console.WriteLine(string.Format("The drive with the name {0} has been removed.", e.ChangedDrive.Name));
}
}
The RemoveableDriveWatcher class looks like this:
/// <summary>
/// Repesents a watcher class for removable drives.
/// </summary>
public class RemovableDriveWatcher
{
/// <summary>
/// Represents the watcher thread which watches for new drives.
/// </summary>
private Thread watcherThread;
/// <summary>
/// Continas all found logical drives of this system.
/// </summary>
private List<DriveInfo> foundDrives;
/// <summary>
/// Initializes a new instance of the <see cref="RemovableDriveWatcher"/> class.
/// </summary>
public RemovableDriveWatcher()
{
this.foundDrives = new List<DriveInfo>();
this.watcherThread = new Thread(new ThreadStart(ScanLogicalDrives));
this.WaitBetweenScansDelay = 1000;
}
/// <summary>
/// Is fired if a new drive has been detected.
/// </summary>
public event EventHandler<RemovableDriveWatcherEventArgs> NewDriveFound;
/// <summary>
/// Is fired if a drive has been removed.
/// </summary>
public event EventHandler<RemovableDriveWatcherEventArgs> DriveRemoved;
/// <summary>
/// Gets or sets the delay in ms between two scans.
/// </summary>
public int WaitBetweenScansDelay
{
get;
set;
}
/// <summary>
/// Starts the watcher.
/// </summary>
public void Start()
{
if (!this.watcherThread.IsAlive)
{
this.watcherThread.Start();
}
}
/// <summary>
/// Stops the watcher.
/// </summary>
public void Stop()
{
if (this.watcherThread.IsAlive)
{
this.watcherThread.Abort();
this.watcherThread.Join();
}
}
/// <summary>
/// Scans for logical drives and fires an event every time a new
/// drive has been found or a drive was removed.
/// </summary>
private void ScanLogicalDrives()
{
DriveInfo[] drives;
do
{
drives = DriveInfo.GetDrives();
// Check for new drives
foreach (DriveInfo drive in drives)
{
if (!(drive.DriveType == DriveType.Removable))
{
continue;
}
if (!drive.IsReady)
{
continue;
}
if (!this.foundDrives.ContainsWithName(drive))
{
this.foundDrives.Add(drive);
if (this.NewDriveFound != null)
{
this.NewDriveFound(this, new RemovableDriveWatcherEventArgs(drives, drive));
}
}
}
// Check for removed drives
for (int i = this.foundDrives.Count - 1; i >= 0; i--)
{
DriveInfo drive = this.foundDrives[i];
if (!drives.ContainsWithName(drive))
{
if (this.DriveRemoved != null)
{
this.DriveRemoved(this, new RemovableDriveWatcherEventArgs(drives, drive));
}
this.foundDrives.RemoveWithName(drive);
}
}
// Sleep
Thread.Sleep(this.WaitBetweenScansDelay);
}
while (true);
}
}
For everything to work you need the RemovableDriveWatcherEventArgs:
/// <summary>
/// Represents the RemovableDriveWatcherEventArgs
/// </summary>
public class RemovableDriveWatcherEventArgs : EventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="RemovableDriveWatcherEventArgs"/> class.
/// </summary>
/// <param name="allDrives">All currently available logical drives in the system.</param>
/// <param name="changedDrive">The changed drive.</param>
public RemovableDriveWatcherEventArgs(DriveInfo[] allDrives, DriveInfo changedDrive)
{
this.Drives = allDrives;
this.ChangedDrive = changedDrive;
}
/// <summary>
/// Gets the changed logical drive that has either been detected or removed.
/// </summary>
public DriveInfo ChangedDrive { get; private set; }
/// <summary>
/// Gets all currently available logical drives.
/// </summary>
public DriveInfo[] Drives { get; private set; }
}
And of course the Extensions:
/// <summary>
/// Contains extensions used by the RemovableDriveWatcher class.
/// </summary>
public static class RemovableDriveWatcherExtensions
{
/// <summary>
/// Extends the DiveInfo[] by the ContainsWithName method.
/// </summary>
/// <param name="all">The array where we want to find the specified instance.</param>
/// <param name="search">The instance which we want to find in the array.</param>
/// <returns>TRUE if the specified instance was found, FALSE if the specified instance was not found.</returns>
public static bool ContainsWithName(this DriveInfo[] all, DriveInfo search)
{
for (int i = 0; i < all.Length; i++)
{
if (all[i].Name == search.Name)
{
return true;
}
}
return false;
}
/// <summary>
/// Extends the List<DriveInfo> by the ContainsWithName method.
/// </summary>
/// <param name="all">The array where we want to find the specified instance.</param>
/// <param name="search">The instance which we want to find in the list.</param>
/// <returns>TRUE if the specified instance was found, FALSE if the specified instance was not found.</returns>
public static bool ContainsWithName(this List<DriveInfo> all, DriveInfo search)
{
for (int i = 0; i < all.Count; i++)
{
if (all[i].Name == search.Name)
{
return true;
}
}
return false;
}
/// <summary>
/// Extends the List<DriveInfo> by the RemoveWithName method.
/// </summary>
/// <param name="all">The array where we want to removed the specified instance.</param>
/// <param name="search">The instance which we want to remove in the list.</param>
public static void RemoveWithName(this List<DriveInfo> all, DriveInfo search)
{
for (int i = 0; i < all.Count; i++)
{
if (all[i].Name == search.Name)
{
all.RemoveAt(i);
return;
}
}
}
}
I hope this helps a little bit.
You don't give much detail, but it seems likely you could call DriveInfo.GetDrives() which returns an array of type DriveInfo[]
DriveInfo has an IsReady() method. Presumably once you check that the drive is ready, you can look for a well-known file on the USB drive() to verify they have mounted the correct USB
You could poll in a loop till you find what you want, but if you don't find what you want in say 60 seconds, you will need to notify the user that you can't find the USB drive you need.
I do not see a ready check inside of the if statement. According to MSDN:
IsReady indicates whether a drive is ready. For example, it indicates
whether a CD is in a CD drive or whether a removable storage device is
ready for read/write operations. If you do not test whether a drive is
ready, and it is not ready, querying the drive using DriveInfo will
raise an IOException.
Are you checking for an IOException? I do not see an IsReady event so you may have to spinwait or hook into the lower level Windows API to find an event to indicate drive readiness. Here's an idea for the meantime:
try
{
DriveInfo[] ListDrives = DriveInfo.GetDrives();
foreach (DriveInfo Drive in ListDrives)
{
if(!Drive.IsReady)//spin
if(Drive.DriveType == DriveType.Removable)
{
// double check it's valid and copy over stuff
}
}
}
catch(IOException ex)//...
I don't have any way to test this right now. Please let me know how it works out for you or if there's more details I need to be aware of.
However, because you are starting this process on startup there's always the possibility that IsReady will not be sufficient and once again you may have to find something else (Windows API I imagine). I have not discovered any documentation that say's anything to the effect.
The c# app that I'm working on uses an ObservableDictionary. The performance on this is not nearly fast enough to accommodate it's functionality. The ObservableDictionary is very rapidly being interacted with (Removing, Adding, and Updating elements) and has to sort every single time a change is made. Is there an alternative I can use to ObservableDictionary that would focus on performance and still be able to sort rapidly?
It's very simple to write your own that does not cause a rebinding each time an add / update or delete is done. We wrote our own because of this very reason. Essentially what we do is to disable the notification that a change has been made until all objects in the collection have been processed, and then we generate the notification. It looks something like this. As you can see, we use MvvmLight as our MVVM framework library. This will improve performance tremendously.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using FS.Mes.Client.Mvvm.MvvmTools.MvvmLight;
namespace FS.Mes.Client.Mvvm.MvvmTools
{
/// <summary>
/// Our implementation of ObservableCollection. This fixes one significant limitation in the .NET Framework implementation. When items
/// are added or removed from an observable collection, the OnCollectionChanged event is fired for each item. Depending on how the collection
/// is bound, this can cause significant performance issues. This implementation gets around this by suppressing the notification until all
/// items are added or removed. This implementation is also thread safe. Operations against this collection are always done on the thread that
/// owns the collection.
/// </summary>
/// <typeparam name="T">Collection type</typeparam>
public class FsObservableCollection<T> : ObservableCollection<T>
{
#region [Constructor]
/// <summary>
/// Constructor
/// </summary>
public FsObservableCollection()
{
DispatcherHelper.Initialize();
}
#endregion
#region [Public Properties]
/// <summary>
/// Gets or sets a property that determines if we are delaying notifications on updates.
/// </summary>
public bool DelayOnCollectionChangedNotification { get; set; }
#endregion
/// <summary>
/// Add a range of IEnumerable items to the observable collection and optionally delay notification until the operation is complete.
/// </summary>
/// <param name="items"></param>
/// <param name="delayCollectionChangedNotification">Value indicating whether delay notification will be turned on/off</param>
public void AddRange(IEnumerable<T> items, bool delayCollectionChangedNotification = true)
{
if (items == null)
throw new ArgumentNullException("items");
DoDispatchedAction(() =>
{
DelayOnCollectionChangedNotification = delayCollectionChangedNotification;
// Do we have any items to add?
if (items.Any())
{
try
{
foreach (var item in items)
this.Add(item);
}
finally
{
// We're done. Turn delay notification off and call the OnCollectionChanged() method and tell it we had a 'dramatic' change
// in the collection.
DelayOnCollectionChangedNotification = false;
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
});
}
/// <summary>
/// Clear the items in the ObservableCollection and optionally delay notification until the operation is complete.
/// </summary>
public void ClearItems(bool delayCollectionChangedNotification = true)
{
// Do we have anything to remove?
if (!this.Any())
return;
DoDispatchedAction(() =>
{
try
{
DelayOnCollectionChangedNotification = delayCollectionChangedNotification;
this.Clear();
}
finally
{
// We're done. Turn delay notification off and call the OnCollectionChanged() method and tell it we had a 'dramatic' change
// in the collection.
DelayOnCollectionChangedNotification = false;
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
});
}
/// <summary>
/// Override the virtual OnCollectionChanged() method on the ObservableCollection class.
/// </summary>
/// <param name="e">Event arguments</param>
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
DoDispatchedAction(() =>
{
if (!DelayOnCollectionChangedNotification)
base.OnCollectionChanged(e);
});
}
/// <summary>
/// Makes sure 'action' is executed on the thread that owns the object. Otherwise, things will go boom.
/// </summary>
///<param name="action">The action which should be executed</param>
private static void DoDispatchedAction(Action action)
{
DispatcherHelper.CheckInvokeOnUI(action);
}
}
}
I have made a class which a form can inherit from and it handles form Location, Size and State. And it works nicely. Except for one thing:
When you maximize the application on a different screen than your main one, the location and size (before you maximized) gets stored correctly, but when it is maximized (according to its previous state) it is maximized on my main monitor. When I then restore it to normal state, it goes to the other screen where it was before. When I then maximize it again, it of course maximized on the correct screen.
So my question is... how can I make a form, when it is maximized, remember what screen it was maximized on? And how do I restore that when the form opens again?
Kind of complete solution to problem
I accepted the answer which had a very good tip about how to if on screen. But that was just part of my problem, so here is my solution:
On load
First get stored Bounds and WindowState from whatever storage.
Then set the Bounds.
Make sure Bounds are visible either by Screen.AllScreens.Any(ø => ø.Bounds.IntersectsWith(Bounds)) or MdiParent.Controls.OfType<MdiClient>().First().ClientRectangle.IntersectsWith(Bounds).
If it doesn't, just do Location = new Point();.
Then set window state.
On closing
Store WindowState.
If WindowState is FormWindowState.Normal, then store Bounds, otherwise store RestoreBounds.
And thats it! =)
Some example code
So, as suggested by Oliver, here is some code. It needs to be fleshed out sort of, but this can be used as a start for whoever wants to:
PersistentFormHandler
Takes care of storing and fetching the data somewhere.
public sealed class PersistentFormHandler
{
/// <summary>The form identifier in storage.</summary>
public string Name { get; private set; }
/// <summary>Gets and sets the window state. (int instead of enum so that it can be in a BI layer, and not require a reference to WinForms)</summary>
public int WindowState { get; set; }
/// <summary>Gets and sets the window bounds. (X, Y, Width and Height)</summary>
public Rectangle WindowBounds { get; set; }
/// <summary>Dictionary for other values.</summary>
private readonly Dictionary<string, Binary> otherValues;
/// <summary>
/// Instantiates new persistent form handler.
/// </summary>
/// <param name="windowType">The <see cref="Type.FullName"/> will be used as <see cref="Name"/>.</param>
/// <param name="defaultWindowState">Default state of the window.</param>
/// <param name="defaultWindowBounds">Default bounds of the window.</param>
public PersistentFormHandler(Type windowType, int defaultWindowState, Rectangle defaultWindowBounds)
: this(windowType, null, defaultWindowState, defaultWindowBounds) { }
/// <summary>
/// Instantiates new persistent form handler.
/// </summary>
/// <param name="windowType">The <see cref="Type.FullName"/> will be used as base <see cref="Name"/>.</param>
/// <param name="id">Use this if you need to separate windows of same type. Will be appended to <see cref="Name"/>.</param>
/// <param name="defaultWindowState">Default state of the window.</param>
/// <param name="defaultWindowBounds">Default bounds of the window.</param>
public PersistentFormHandler(Type windowType, string id, int defaultWindowState, Rectangle defaultWindowBounds)
{
Name = string.IsNullOrEmpty(id)
? windowType.FullName
: windowType.FullName + ":" + id;
WindowState = defaultWindowState;
WindowBounds = defaultWindowBounds;
otherValues = new Dictionary<string, Binary>();
}
/// <summary>
/// Looks for previously stored values in database.
/// </summary>
/// <returns>False if no previously stored values were found.</returns>
public bool Load()
{
// See Note 1
}
/// <summary>
/// Stores all values in database
/// </summary>
public void Save()
{
// See Note 2
}
/// <summary>
/// Adds the given <paramref key="value"/> to the collection of values that will be
/// stored in database on <see cref="Save"/>.
/// </summary>
/// <typeparam key="T">Type of object.</typeparam>
/// <param name="key">The key you want to use for this value.</param>
/// <param name="value">The value to store.</param>
public void Set<T>(string key, T value)
{
// Create memory stream
using (var s = new MemoryStream())
{
// Serialize value into binary form
var b = new BinaryFormatter();
b.Serialize(s, value);
// Store in dictionary
otherValues[key] = new Binary(s.ToArray());
}
}
/// <summary>
/// Same as <see cref="Get{T}(string,T)"/>, but uses default(<typeparamref name="T"/>) as fallback value.
/// </summary>
/// <typeparam name="T">Type of object</typeparam>
/// <param name="key">The key used on <see cref="Set{T}"/>.</param>
/// <returns>The stored object, or the default(<typeparamref name="T"/>) object if something went wrong.</returns>
public T Get<T>(string key)
{
return Get(key, default(T));
}
/// <summary>
/// Gets the value identified by the given <paramref name="key"/>.
/// </summary>
/// <typeparam name="T">Type of object</typeparam>
/// <param name="key">The key used on <see cref="Set{T}"/>.</param>
/// <param name="fallback">Value to return if the given <paramref name="key"/> could not be found.
/// In other words, if you haven't used <see cref="Set{T}"/> yet.</param>
/// <returns>The stored object, or the <paramref name="fallback"/> object if something went wrong.</returns>
public T Get<T>(string key, T fallback)
{
// If we have a value with this key
if (otherValues.ContainsKey(key))
{
// Create memory stream and fill with binary version of value
using (var s = new MemoryStream(otherValues[key].ToArray()))
{
try
{
// Deserialize, cast and return.
var b = new BinaryFormatter();
return (T)b.Deserialize(s);
}
catch (InvalidCastException)
{
// T is not what it should have been
// (Code changed perhaps?)
}
catch (SerializationException)
{
// Something went wrong during Deserialization
}
}
}
// Else return fallback
return fallback;
}
}
Note 1: In the load method you have to look for previously stored WindowState, WindowBounds and other values. We use SQL Server, and have a Window table with columns for Id, Name, MachineName (for Environment.MachineName), UserId, WindowState, X, Y, Height, Width. So for every window, you would have one row with WindowState, X, Y, Height and Width for each user and machine. In addition we have a WindowValues table which just has a foreign key to WindowId, a Key column of type String and a Value column of type Binary. If there is stuff that is not found, I just leave things default and return false.
Note 2: In the save method you then, of course do the reverse from what you do in the Load method. Creating rows for Window and WindowValues if they don't exist already for the current user and machine.
PersistentFormBase
This class uses the previous class and forms a handy base class for other forms.
// Should have been abstract, but that makes the the designer crash at the moment...
public class PersistentFormBase : Form
{
private PersistentFormHandler PersistenceHandler { get; set; }
private bool handlerReady;
protected PersistentFormBase()
{
// Prevents designer from crashing
if (LicenseManager.UsageMode != LicenseUsageMode.Designtime)
{
Load += persistentFormLoad;
FormClosing += persistentFormFormClosing;
}
}
protected event EventHandler<EventArgs> ValuesLoaded;
protected event EventHandler<EventArgs> StoringValues;
protected void StoreValue<T>(string key, T value)
{
if (!handlerReady)
throw new InvalidOperationException();
PersistenceHandler.Set(key, value);
}
protected T GetValue<T>(string key)
{
if (!handlerReady)
throw new InvalidOperationException();
return PersistenceHandler.Get<T>(key);
}
protected T GetValue<T>(string key, T fallback)
{
if (!handlerReady)
throw new InvalidOperationException();
return PersistenceHandler.Get(key, fallback);
}
private void persistentFormLoad(object sender, EventArgs e)
{
// Create PersistenceHandler and load values from it
PersistenceHandler = new PersistentFormHandler(GetType(), (int) FormWindowState.Normal, Bounds);
PersistenceHandler.Load();
handlerReady = true;
// Set size and location
Bounds = PersistenceHandler.WindowBounds;
// Check if we have an MdiParent
if(MdiParent == null)
{
// If we don't, make sure we are on screen
if (!Screen.AllScreens.Any(ø => ø.Bounds.IntersectsWith(Bounds)))
Location = new Point();
}
else
{
// If we do, make sure we are visible within the MdiClient area
var c = MdiParent.Controls.OfType<MdiClient>().FirstOrDefault();
if(c != null && !c.ClientRectangle.IntersectsWith(Bounds))
Location = new Point();
}
// Set state
WindowState = Enum.IsDefined(typeof (FormWindowState), PersistenceHandler.WindowState) ? (FormWindowState) PersistenceHandler.WindowState : FormWindowState.Normal;
// Notify that values are loaded and ready for getting.
var handler = ValuesLoaded;
if (handler != null)
handler(this, EventArgs.Empty);
}
private void persistentFormFormClosing(object sender, FormClosingEventArgs e)
{
// Set common things
PersistenceHandler.WindowState = (int) WindowState;
PersistenceHandler.WindowBounds = WindowState == FormWindowState.Normal ? Bounds : RestoreBounds;
// Notify that values will be stored now, so time to store values.
var handler = StoringValues;
if (handler != null)
handler(this, EventArgs.Empty);
// Save values
PersistenceHandler.Save();
}
}
And thats pretty much it. To use it, a form would just inherit from the PersistentFormBase. That would automatically take care of bounds and state. If anything else should be stored, like a splitter distance, you would listen for the ValuesLoaded and StoringValues events and in those use the GetValue and StoreValue methods.
Hope this can help someone! Please let me know if it does. And also, please provide some feedback if there is anything you think could be done better or something. I would like to learn =)
There's no built in way to do this - you'll have to write the logic yourself. One reason for this is that you have to decide how to handle the case where the monitor that the window was last shown on is no longer available. This can be quite common with laptops and projectors, for example. The Screen class has some useful functionality to help with this, although it can be difficult to uniquely and consistently identify a display.
I found a solution to your problem by writing a little functio, that tests, if a poitn is on a connected screen.
The main idea came from
http://msdn.microsoft.com/en-us/library/system.windows.forms.screen(VS.80).aspx
but some modifications were needed.
public static bool ThisPointIsOnOneOfTheConnectedScreens(Point thePoint)
{
bool FoundAScreenThatContainsThePoint = false;
for(int i = 0; i < Screen.AllScreens.Length; i++)
{
if(Screen.AllScreens[i].Bounds.Contains(thePoint))
FoundAScreenThatContainsThePoint = true;
}
return FoundAScreenThatContainsThePoint;
}
There are a few issues with the above solution.
On multiple screens as well as if the restore screen is smaller.
It should use Contains(...), rather than IntersectsWith as the control part of the form might otherwise be outside the screen-area.
I will suggest something along these lines
bool TestBounds(Rectangle R) {
if (MdiParent == null) return Screen.AllScreens.Any(ø => ø.Bounds.Contains(R)); // If we don't have an MdiParent, make sure we are entirely on a screen
var c = MdiParent.Controls.OfType<MdiClient>().FirstOrDefault(); // If we do, make sure we are visible within the MdiClient area
return (c != null && c.ClientRectangle.Contains(R));
}
and used like this. (Note that I let Windows handle it if the saved values does not work)
bool BoundsOK=TestBounds(myBounds);
if (!BoundsOK) {
myBounds.Location = new Point(8,8); // then try (8,8) , to at least keep the size, if other monitor not currently present, or current is lesser
BoundsOK = TestBounds(myBounds);
}
if (BoundsOK) { //Only change if acceptable, otherwise let Windows handle it
StartPosition = FormStartPosition.Manual;
Bounds = myBounds;
WindowState = Enum.IsDefined(typeof(FormWindowState), myWindowState) ? (FormWindowState)myWindowState : FormWindowState.Normal;
}
Try to spawn your main form in its saved location in restored (non-maximized) state, THEN maximize it if the last state was maximized.
As Stu said, be careful about removed monitors in this case. Since the saved location may contain off-screen coordinates (even negative ones), you may effectively end up with and invisible (off-screen, actually) window. I think checking for desktop bounds before loading previous state should prevent this.