Writing a WP7.1 (Mango) app in Silverlight for Windows Phone (XAML / C#). I have an exception that I just cannot shake. I have a ListBox displaying an ObservableCollection<string> which displays the file names of XML files from IsolatedStorage. The Listbox control also contains a ContextMenu from the Windows Phone Toolkit. I use it to delete items from my ListBox, and from disk.
The ContextMenu and my delete method works fine normally... But if, whilst on the same page without navigating away, I create a new file (let's call it File1) then create another file (File2), if I then attempt to delete File1, the IsolateStorageFile.DeleteFile method throws an exception stating "An error occurred while accessing IsolatedStorage" with an inner message of null. HOWEVER, if I create File1, then File2. Then delete File2 then File1, it works just fine! ARGH!
If I leave the Page or restart the app again, I can delete the file no problems.
I've stripped back the code to hopefully make it a bit easier to read.
UI Binding Collection field in the code behind.
ObservableCollection<string> Subjects;
Click event calls the write method.
private void Button_Click_AddNewSubject(object sender, RoutedEventArgs e)
{
if (TryWriteNewSubject(NewSubjectNameTextBox.Text))
{
... Manipulate UI
}
}
Method to Add file to IsoStore and Subjects collection. Returns a bool for conditional UI manipulation.
private bool TryWriteNewSubject(string subjectName)
{
... file name error checking
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
store.OpenFile(subjectName + ".xml", FileMode.CreateNew);
store.Dispose();
}
Subjects.Add(subjectName);
return true;
}
else return false;
}
else return false;
}
The ContextMenu click event calls the delete file method
private void ContextMenuButton_Click(object sender, RoutedEventArgs e)
{
string subjectName = (sender as MenuItem).DataContext as string;
DeleteFile(subjectName);
}
And my delete method
private void DeleteFile(string subjectName)
{
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
string fileName = subjectName + ".xml";
store.DeleteFile(fileName);
Subjects.Remove(subjectName);
}
}
The code is straight forward, I just don't know what I'm missing. :(
You get a IsolatedStorageFileStream from OpenFile. You need to dispose it before another operation can manipulate it.
Btw, a using statement calls dispose for you, so there's no need to call dispose at the end of a using statement.
Related
This is my primary way for displaying help topics from within my WinForm button click handlers:
Handler:
private void buttonHelp_Click(object sender, EventArgs e)
{
CutTools.DisplayHelpTopic(this, "create-new-viewport.htm");
}
Base method:
public static void DisplayHelpTopic(Control parent, string topic)
{
try
{
// Use an empty form as the parent so that the help file will not block the CAD software
Form mHelpParent = new Form();
// Use location of this DLL file
System.Reflection.Module mod = parent.GetType().Module;
string path = Path.GetDirectoryName(mod.FullyQualifiedName);
Help.ShowHelp(mHelpParent,
Path.Combine(path, "cut-tools-help.chm"), HelpNavigator.Topic, topic);
}
catch (System.Exception ex)
{
_AcAp.Application.ShowAlertDialog(
string.Format("\nError: {0}\nStackTrace: {1}", ex.Message, ex.StackTrace));
}
}
The forms are displaid inside AutoCAD, BricsCAD or ZWCAD. The about is fine and great. But if I want to simply display the CHM file itself (so no actual form is available) I have to do this:
[CommandMethod("TS_DisplayHelp")]
public void TS_DisplayHelp()
{
// Use location of this DLL file
System.Reflection.Module mod = GetType().Module;
System.Diagnostics.Process.Start(
Path.Combine(Path.GetDirectoryName(mod.FullyQualifiedName), "cut-tools-help.chm"));
}
It works but has one drawback. It spawns a new instance of the help and does not use the same instance.
For example:
You start one of the other commands and show the help via button click. You cancel.
You start a different command and show the help via button click. Help.ShowHelp uses same instance.
You can command and start help via TS_DISPLAYHELP and it starts new instance.
Given the context of TS_DISPLAYHELP I can't work out how to directly use Help.ShowHelp as I can in my button click handlers.
At the moment I have managed to get around this issue by duplicating the DisplayHelpTopic code directly in the command TS_DISPLAYHELP method:
[CommandMethod("TS_DisplayHelp")]
public void TS_DisplayHelp()
{
try
{
// Use an empty form as the parent so that the help file will not block the CAD software
Form mHelpParent = new Form();
// Use location of this DLL file
System.Reflection.Module mod = GetType().Module;
string path = Path.GetDirectoryName(mod.FullyQualifiedName);
Help.ShowHelp(mHelpParent,
Path.Combine(path, "cut-tools-help.chm"), HelpNavigator.Topic, "command-index.htm");
}
catch (System.Exception ex)
{
_AcAp.Application.ShowAlertDialog(
string.Format("\nError: {0}\nStackTrace: {1}", ex.Message, ex.StackTrace));
}
}
I know that my default topic is "command-index.htm".
I am happy with the above resolution.
I build a windows-forms-app where I (try to) do extensive calculations on images whenever they are created in a specific directory which I watch using the FileSystemWatcher.
private void OnNewFileInDir(object source, FileSystemEventArgs evtArgs)
{
//Load the actual image:
imageFilepath = evtArgs.FullPath; //imageFilepath is a private class string var
Image currentImage = Image.FromFile(imageFilepath);
//Display the image in the picture box:
UpdatePictureBox(currentImage); //Method to update the GUI with invoking for the UI thread
//Extensive Calculation on the images
Image currentResultImage = DoExtensiveWork(currentImage);
// Put the current result in the picture box
UpdatePictureBox(currentResultImage );
//dispose the current/temporary image
currentImage.Dispose();
}
The event is fired correctly when pasting a new file into the directory. But I get a "System.OutOfMemoryException" on the line
Image currentImage = Image.FromFile(imageFilepath);
When I put exactly this code (using the same filepath) in a button event (so not using the FileSystemWatcher) everything works fine. So I thought there is some issue regarding the thread since the extensive calculation is then called by the FileSystemWatcher-Thread not by the UI thread.
I tried things like:
//TRY 1: By executing a button click method containg the code
pb_Calculate_Click(this, new EventArgs()); //This does not work eigther --> seems to be a problem with "Who is calling the method"
//TRY 2: Open a new dedicated thread for doing the work of the HistoCAD calculations
Thread newThread_OnNewFile = new Thread(autoCalcAndDisplay);
newThread_OnNewFile.Start();
//TRY 3: Use a background worker as a more safe threading method(?)
using (BackgroundWorker bw = new BackgroundWorker())
{
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
if (bw.IsBusy == false)
{
bw.RunWorkerAsync();
}
}
Unfortunalty none of them worked reliable. 1st not at all. 2nd works only from time to time and 3rd one as well.
Do some of you know whats going on there? What can I do to make it work correctly? Thanks!
EDIT:
Thanks for the comments:
I also tried to call GC.collect() on every event and tried to include using() and dispose() wherever I can. When I'm doing the process manually (with buttons) it works even when processing a lot of files one after another. But when done with the eventhandler I sometimes get the outOfMem-Exception even on the very first file I copy in the folder. File is always the same BMP with 32MB. This is the memory usage for processing one image:
EDIT 2:
I created a minimal example (GUI with one picture Box and one Checkbox in buttonstyle). It turns out that the same thing is happening. The OutOfMemException occured at the same line (Image...). Especially for large BMPs the exception occours nearly always:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace MinimalExampleTesting
{
public partial class Form1 : Form
{
private string imageFilepath;
private string autoModePath = #"C:\Users\Tim\Desktop\bmpordner";
//Define a filesystem watcher object
private FileSystemWatcher watcher;
public Form1()
{
InitializeComponent();
/*** Creating as FileSystemEventArgs watcher in order to monitor a specific folder ***/
watcher = new FileSystemWatcher();
Console.WriteLine(watcher.Path);
// set the path if already exists, otherwise we have to wait for it to be set
if (autoModePath != null)
watcher.Path = autoModePath;
// Watch for changes in LastAccess and LastWrite times and renaming of files or directories.
watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
| NotifyFilters.FileName | NotifyFilters.DirectoryName;
// Only watch for BMP files.
watcher.Filter = "*.bmp";
// Add event handler. Only on created, not for renamed, changed or something
// Get into the list of the watcher. Watcher fires event and "OnNewFileCreatedInDir" will be called
watcher.Created += new FileSystemEventHandler(OnNewFileInDir);
}
private void tb_AutoMode_CheckedChanged(object sender, EventArgs e)
{
//First of all test if the auto mode path is set and correctly exists currently:
if (!Directory.Exists(autoModePath) || autoModePath == null)
{
MessageBox.Show("Check if Auto Mode path is correctly set and if path exists",
"Error: Auto Mode Path not found");
return;
}
// Begin watching if the AutoModePath was at least set
if (autoModePath != null)
{
watcher.EnableRaisingEvents = tb_AutoMode.Checked; //Since we have a toogle butten, we can use the 'checked' state to enable or disable the automode
}
}
private void OnNewFileInDir(object source, FileSystemEventArgs evtArgs)
{
Console.WriteLine("New file in detected: " + evtArgs.FullPath);
//Force a garbage collection on every new event to free memory and also compact mem by removing fragmentation.
GC.Collect();
//Set the current filepath in the class with path of the file added to the folder:
imageFilepath = evtArgs.FullPath;
//Load the actual image:
Image currentImage = Image.FromFile(imageFilepath);
UpdatePictureBox(currentImage);
}
private void UpdatePictureBox(Image img)
{
if (pictureBox_Main.InvokeRequired)
{
MethodInvoker mi = delegate
{
pictureBox_Main.Image = img;
pictureBox_Main.Refresh();
};
pictureBox_Main.Invoke(mi);
}
else { //Otherwise (when the calculation is perfomed by the GUI-thread itself) no invoke necessary
pictureBox_Main.Image = img;
pictureBox_Main.Refresh();
}
img.Dispose();
}
}
}
Thanks in advance for further hints :)
SOLVED:
The issue seems to be, that event is fired immediately but the file is not yet finally copied. That means we have to wait until the file is free.
A Thread.Sleep(100) at the start of the event does the job. As I now know what to google for, I found two links:
This and this where you can find:
The OnCreated event is raised as soon as a file is created. If a file is being copied or transferred into a watched directory, the OnCreated event will be raised immediately, followed by one or more OnChanged events
So, what works best for my case, was to include a method to test if the file is still locked and than wait at the beginning of the event for an unlock of the file. No need for an additional thread or a BackgroundWorker.
See the code:
private void OnNewFileInDir(object source, FileSystemEventArgs evtArgs)
{
Console.WriteLine("New file detected: " + evtArgs.FullPath);
//Wait for the file to be free
FileInfo fInfo = new FileInfo(evtArgs.FullPath);
while (IsFileLocked(fInfo))
{
Console.WriteLine("File not ready to use yet (copy process ongoing)");
Thread.Sleep(5); //Wait for 5ms
}
//Set the current filepath in the class with path of the file added to the folder:
imageFilepath = evtArgs.FullPath;
//Load the actual image:
Image currentImage = Image.FromFile(imageFilepath);
UpdatePictureBox(currentImage);
}
private static bool IsFileLocked(FileInfo file)
{
FileStream stream = null;
try
{
//try to get a file lock
stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None);
}
catch (IOException)
{
//File isn't ready yet, so return true as it is still looked --> we need to keep on waiting
return true;
}
finally
{
if (stream != null){
stream.Close();
stream.Dispose();
}
}
// At the end, when stream is closed and disposed and no exception occured, return false --> File is not locked anymore
return false;
}
Nevertheless: Thanks for your help...it got me on the right track;)
As MSDN says about FileSystemWatcher:
Common file system operations might raise more than one event. For example, when a file is moved from one directory to another, several OnChanged and some OnCreated and OnDeleted events might be raised. Moving a file is a complex operation that consists of multiple simple operations, therefore raising multiple events. Likewise, some applications (for example, antivirus software) might cause additional file system events that are detected by FileSystemWatcher.
Maybe your Image is loaded severals times.
To test it, you can add this line after imageFilepath = evtArgs.FullPath;
imageFilepath = evtArgs.FullPath;
Task.Run(()=>{MessageBox.Show(imageFilepath);});
This will inform you about the fact that the Created event is fired, and will not hold up your program.
Edit
Put your line of code that give the OutOfMemory in a Try Catch.
Like this and this questions describes, you can get this error if your image is corrupt.
I am currently working on Windows Store App in c#.
Now,
I am having a list box 'Listbox1' which gets its items on a button click event from a text box 'tasks', and have selected Items delete property on other button click event.
private void add_Click(object sender, RoutedEventArgs e)
{
string t;
t = tasks.Text;
if (t != "")
{
Listbox1.Items.Add(t);
}
else
{
var a = new MessageDialog("Please Enter the Task First");
a.Commands.Add(new UICommand("Ok"));
a.ShowAsync();
}
tasks.Text = "";
}
private void del_Click(object sender, RoutedEventArgs e)
{
for (int p = 0; p < Listbox1.SelectedItems.Count; p++)
{
Listbox1.Items.Remove(Listbox1.SelectedItems[p].ToString());
p--;
}
}
Now I want to save this list into local application storage, after user complete the changes (on a button click event perhaps).
And also to send all Listbox Items to another page(s).
I am not much a coder, I design things.
Please guide me by sample or reference.
Thank you in advance :)
If you have already stored the data to local storage, you could just read it in the OnNavigatedTo override of the other page. Otherwise, use the navigation parameter: http://social.msdn.microsoft.com/Forums/windowsapps/en-US/8cb42356-82bc-4d77-9bbc-ae186990cfd5/passing-parameters-during-navigation-in-windows-8
Edit: I am not sure whether you also need some information about local storage. This is easy: Windows.Storage.ApplicationData.Current.LocalSettings has a property called Values, which is a Dictionary you can write your settings to. Have a look at http://msdn.microsoft.com/en-us/library/windows/apps/hh700361.aspx
Edit: Try something like this code to store your list.
// Try to get the old stuff from local storage.
object oldData = null;
ApplicationDataContainer settings = ApplicationData.Current.LocalSettings;
bool isFound = settings.Values.TryGetValue("List", out oldData);
// Save a list to local storage. (You cannot store the list directly, because it is not
// serialisable, so we use the detours via an array.)
List<string> newData = new List<string>(new string[] { "test", "blah", "blubb" });
settings.Values["List"] = newData.ToArray();
// Test whether the saved list contains the expected data.
Debug.Assert(!isFound || Enumerable.SequenceEqual((string[]) oldData, newData));
Note, this is only demo code for testing - it does not make real sense...
Edit: One advice: Do not persist the list in your click handlers as this will become extremely slow as the list grows. I would load and save the list in the Navigation handlers, i.e. add something like
protected override void OnNavigatedTo(NavigationEventArgs e) {
base.OnNavigatedTo(e);
if (this.ListBox1.ItemsSource == null) {
object list;
if (ApplicationData.Current.LocalSettings.Values.TryGetValue("List", out list)) {
this.ListBox1.ItemsSource = new List<string>((string[]) list);
} else {
this.ListBox1.ItemsSource = new List<string>();
}
}
}
protected override void OnNavigatedFrom(NavigationEventArgs e) {
if (this.ListBox1.ItemsSource != null) {
ApplicationData.Current.LocalSettings.Values["List"] = this.ListBox1.ItemsSource.ToArray();
}
base.OnNavigatedFrom(e);
}
Here is very nice simple example on SQLite DataBase Use in winRT app Development. Look at it and you will know how you can store your Data on the Local Machine. I learned Basic code from this example.
http://blogs.msdn.com/b/robertgreen/archive/2012/11/13/using-sqlite-in-windows-store-apps.aspx
Now, for ease of navigation let me suggest you a flow for this portion of your app.
take one ObservableCollection<> of string and store values of
that textBox into this ObservationCollection with onClick() and then
refer that ObservableCollection<String> to the ItemsList of the
listBox.
now at the time you need to send your Data to the next page, make one parameterised constructor of next page and pass that ObservableCollection<String> as it's parameter.
Now you can access those Data in your constructor and can use as however you want.
Hope this will help..
This is kinda strange, let me try to explain it as best as possible:
When I create a new file and Save it, it saves correctly (test.xml).
When I make changes to this file and Save it, it saves correctly (to test.xml)
When I make changes again to this file or just choose Save As, it works correctly (newtest.xml)
However, when I do a file open, make changes to a file (test.xml) and click Save it is saving to (newtest.xml).
This is in my MainForm.cs
if (this.openEditorDialog1.ShowDialog(this) == DialogResult.OK && editForm != null)
{
editForm.Close();
editForm = new EditorForm(this);
editForm.OpenFile(this.openEditorDialog1.FileName);
editForm.Closing += new CancelEventHandler(EditorForm_Closing);
editForm.MdiParent = this;
editForm.Show();
}
private void biFileSave_Click(object sender, EventArgs e)
{
if (!editForm.HasFileName)
{
if (this.saveEditorDialog1.ShowDialog(this) == DialogResult.OK)
{
this.ActiveDiagram.SaveSoap(this.saveEditorDialog1.FileName);
editForm.FileName = this.saveEditorDialog1.FileName;
}
}
else
{
this.ActiveDiagram.SaveSoap(this.saveEditorDialog1.FileName);
}
This is in my EditorForm.cs
public void OpenFile(string strFileName)
{
diagramComponent.LoadSoap(mainForm.openEditorDialog1.FileName);
this.FileName = mainForm.openEditorDialog1.FileName;
this.tabControl1.SelectedTab = DiagramTab;
}
I'm sure it has to do with the what I'm doing in the EditoForm but I can't seem to figure it out.
else
{
this.ActiveDiagram.SaveSoap(this.saveEditorDialog1.FileName);
It looks like you want:
this.ActiveDiagram.SaveSoap(editForm.FileName);
It must have to do with mainForm.openEditorDialog1.FileName. Use a FileName property of the form that does the saving. When you open the file, set the fileName to mainForm.openEditorDialog1.FileName. When you SaveAs, set the FileName property there, too. This way, whenever the current file, changes you set the FileName property appropriately. Then, when it comes time to save the file, you always have the correct filename.
In summary, only use the .FileName property of the SaveAs dialog or the FileOpen dialog right after you use them.
Problem is that application closes without any error, VS stays opened.
I have multiple dynamically created FileSystemWatchers, all of them have eventhandler on "Created" event. So this eventhandler method looks like this :
void watcher_FileCreated(object sender, FileSystemEventArgs e)
{
FileInfo f1 = new FileInfo(e.FullPath);
filesDataGrid.Rows.Add(f1.Name);
foreach (TLPclass table in parameterForm.getParameters)
{
//uses some funcion form another class
}
}
Line which causes program to close is the one where I'm adding File name to DataGridView - filesDataGrid.Rows.Add(f1.Name);
Also runs OK without that line.
Weird thing is that application runs normally, when launched from .exe file in projects folder. I can't see error in my code, but I guess theres something awfully wrong with it, if it doesn't even show error message.
And - what are the most common reasons why program could just shut down with no warnings?
The FileSystemWatcher will trigger the events in a separate thread. The logic inside the event handlers will need to take that fact in consideration and perform any synchronization needed. So you'll need something like this:
private void watcher_FileCreated(object sender, FileSystemEventArgs e)
{
if (filesDataGrid.InvokeRequired)
{
filesDataGrid.Invoke((MethodInvoker)delegate { watcher_FileCreated(sender, e); });
}
else
{
FileInfo f1 = new FileInfo(e.FullPath);
filesDataGrid.Rows.Add(f1.Name);
foreach (TLPclass table in parameterForm.getParameters)
{
//uses some funcion form another class
}
}
}
Wrap your function with a Try{}catch (Exception ex) { } block.
i Would think it is because DataGridRow needs to be created with the variables from the FileSystemWatcher first.
e.g
DataGridViewRow row = filesDataGrid.NewRow();
row["columnname"] = f1.name;
filesDataGrid.Rows.Add(row);