I am using a WinForms PropertyGrid to display various configuration settings for a program. The PropertyGrid is bound to an XML document which has been Xmlserializer.Deserialize-ed. This allows a user to type in new values, which then get serialized back into the config.xml file. In some cases, these properties are just numbers, and typing in values makes sense. However, in other cases, the values are file names or directory paths, so it makes much more sense to have these entered through an OpenFileDialog or FolderBrowserDialog.
What I'd like to have happen is that the if user clicks on a folder or filename cell in the PropertyGrid, the UI will open the appropriate dialog, get a result, and enter that result into the grid, replacing the existing value. The trouble is, PropertyGrid doesn't seem to allow access to the controls inside it, so I can't respond to an OnClicked event.
Here's how I would like the code to work (EDIT: updated code):
private void propertyGrid_config_Click(object sender, EventArgs e)
{
PropertyGrid grid = (PropertyGrid)sender;
PropertyDescriptor selectedItem = grid.SelectedGridItem.PropertyDescriptor;
if (selectedItem.Category == "Files & Folders")
{
if (selectedItem.DisplayName.Contains("directory"))
{
FolderBrowserDialog folder = new FolderBrowserDialog();
if (folder.ShowDialog() == DialogResult.OK)
{
selectedItem.SetValue(grid.SelectedObject, folder.SelectedPath);
grid.Refresh();
}
}
else if (selectedItem.DisplayName.Contains("file"))
{
OpenFileDialog file = new OpenFileDialog();
if (file.ShowDialog() == DialogResult.OK)
{
selectedItem.SetValue(grid.SelectedObject, file.FileName);
grid.Refresh();
}
}
}
}
I've set the grid's "Clicked" event to this handler, but obviously that doesn't work since that only handles the container and not what's in it. (Note this handler works fine if I base it on the "PropertyChanged" event, but that's obviously not what I'm looking for.)
Is there some way to get access to the components and create the events I want? How would you conquer this issue?
In case it's relevant, here's some of the code for the PropertyGrid:
The grid exists in a class called "Configuration" which defines all the properties like this:
[Description("Folder for storing Bonding Key logs")]
[Category("Files & Folders")]
[DisplayName("Log output directory")]
public string dirLogOutput { get; set; }
The XML file will have a corresponding entry for each Property like this:
<dirLogOutput>C:\Users\AHoffman\Desktop\TestData</dirLogOutput>
The Serializer does a good job of matching data from the XML file to the grid, and vice-versa:
public Configuration TryLoadConfiguration(Configuration thisConfig)
{
string filename = GetConfigFilename();
try
{
if (!File.Exists(filename))
{
thisConfig.setDefaults();
}
else
{
using (var stream = File.Open(filename, FileMode.Open, FileAccess.Read))
{
var serializer = new XmlSerializer(typeof(Configuration));
thisConfig = (Configuration)serializer.Deserialize(stream);
}
}
}
catch (Exception ex)
{
MessageBox.Show("Failed to load configuration file during startup: " + ex.Message);
thisConfig.setDefaults();
}
return thisConfig;
}
private void SaveConfiguration(string filename, Configuration thisConfig)
{
try
{
using (var stream = File.Open(filename, FileMode.Create, FileAccess.Write))
{
var serializer = new XmlSerializer(typeof(Configuration));
serializer.Serialize(stream, thisConfig);
}
}
catch (Exception ex)
{
MessageBox.Show("Failed to save configuration file: " + ex.Message);
}
}
I note that a question like this has been asked before here but with no answers. Hopefully I'm giving you enough info to get something back.
OK never mind. I found answers to my question here (for files) and here (for folders).
It all relies in using a custom UITypeEditor for each type.
[EditorAttribute(typeof(OpenFileNameEditor), typeof(System.Drawing.Design.UITypeEditor))]
[EditorAttribute(typeof(FolderNameEditor2), typeof(System.Drawing.Design.UITypeEditor))]
Thanks greatly to #Simon Mourier, #Stewy, and #tzup for their answers.
Related
I am a beginner in C# WPF, and I am trying to create an app, which uses DataTable and writes this DataTable to an XML file using DataTable.WriteXML() method. Then the XML file is being read by the DataGrid from the ListView (selectedList).
private void refreshDataGrid(DataGrid dataGridName, ListView selectedList, string path)
{
FileStream file;
//check if the item from ListView is selected
if (selectedList.SelectedItem != null)
{
try
{
file = File.Open(path + "\\" + selectedList.SelectedItem.ToString() + ".xml", FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
}
catch (Exception x)
{
MessageBox.Show(x.Message.ToString());
return;
}
//clear the defaultData DataSet so it doesn't have any content
defaultData.Tables.Clear();
//read XML file
defaultData.ReadXml(file);
file.Close();
dataGridName.ItemsSource = defaultData.Tables[0].DefaultView;
}
DataGrid is editable by the user and when he does Edit the content, It is saved to an XML file, and then FileWatcher is watching for changes in Last Access to the file, so when the content of a file is changing, the datagrid will update.
FileSystemWatcher fileWatcher = new();
private void CreateFileWatcher(string path)
{
fileWatcher.Path = path;
fileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.LastAccess | NotifyFilters.Size | NotifyFilters.FileName;
fileWatcher.Filter = "*.xml";
fileWatcher.Changed += FileWatcher_Changed;
fileWatcher.EnableRaisingEvents = true;
}
// Event for filewatcher throws an Exception
private void FileWatcher_Changed(object sender, FileSystemEventArgs e)
{
Application.Current.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)(() =>
{
try
{
refreshDataGrid(dataGridName, userListView, path);
}
catch(Exception)
{
return;
}
}));
}
So the issue is, when i create a NEW XML FILE while the app is running, and then user opens this file in a DataGrid and edits a field in the DataGrid, the data is saved to an XML file as it should be, and FileWatcher is Invoked, so when the FileWatcher is trying to refresh the Datagrid I get this message:
Could not find UIElementAutomationPeer.cs
You need to find
UIElementAutomationPeer.cs to view the source for the current call
stack frame System.ArgumentNullException: „Value cannot be null.
Arg_ParamName_Name”.
Unhandled exception of type „System.ArgumentNullException” in PresentationCore.dll
Value cannot be null.
When i create an XML file, and restart the app, everything works normally. Also when the file was created before launching the app, FileWatcher is working fine. I guess newly created files (during app runtime) are not updated to the app itself, and it needs to refresh the UI but I don't know how. My FileWatcher is also global, so maybe that could be an issue, or should I refresh the FileWatcher somehow? Any help would be appreciated.
EDIT: I think there is a problem while creating a file in my program, probably the app wants to use the same file twice or can't access it, here is the code:
private void saveTableToXML()
{
DataTable dt = new DataTable();
dt = ((DataView)dataGridName.ItemsSource).ToTable();
try
{
FileStream file = File.Create(path + "\\.xml");
dt.WriteXml(file, XmlWriteMode.WriteSchema,false);
file.Close();
}
catch(Exception ex)
{
MessageBox.Show(ex.Message.ToString());
}
}
I use this code here to create a user-scoped setting during runtime:
System.Configuration.SettingsProperty userScopedProperty =
new System.Configuration.SettingsProperty("New Setting");
userScopedProperty.DefaultValue = "This setting default value";
userScopedProperty.IsReadOnly = false;
userScopedProperty.PropertyType = typeof(string);
userScopedProperty.Provider =
Properties.Settings.Default.Providers["LocalFileSettingsProvider"];
userScopedProperty.Attributes.Add(typeof(System.Configuration.UserScopedSettingAttribute),
new System.Configuration.UserScopedSettingAttribute());
Properties.Settings.Default.Properties.Add(userScopedProperty);
Properties.Settings.Default["New Setting"] = "value changed to this";
Properties.Settings.Default.Save();
Properties.Settings.Default.Reload();
Which for some reason only sometimes works and will create the settings in the userSettings section in the user.config file. While executing in the same session this works:
Properties.Settings.Default["New Setting"]
But as soon as I close the application and the start it up again, that line of code will give me a System.Configuration.SettingsPropertyNotFoundException. I have tried adding Properties.Settings.Default.Reload() right before attempting to read the setting but that doesnt seem to work either.
The goal of this portion of the project is to be able to create user settings during runtime, close the program, and when you start the program again you can view those settings. You should also be able to change them at any point. I run the program in Visual Studio in debug mode, not sure if that information is needed.
So my questions are:
How do I properly create during runtime and read the properties that are stored in the user.config file that I saved in a previous session? Is there a better way to do this?
I generally keep settings like that in an XML file in IsolatedStorage. I create a ProgramSettings class that I annotate for XML serialization (using XmlRootAttribute, XmlElementAttribute, and sometimes XmlArrayAttribute and XmlArrayItemAttribute) and then I use this class to read and write that XML settings file:
public static class PersistedSettings<T> where T : class, new()
{
public static T LoadSettings(string applicationName, ISettingsErrorReporter errorReporter)
{
var filename = GetFileName(applicationName);
try
{
using (var isoStore = IsolatedStorageFile.GetStore(IsolatedStorageScope.User | IsolatedStorageScope.Domain | IsolatedStorageScope.Assembly, null, null))
{
if (!isoStore.FileExists(filename))
{
return new T();
}
//otherwise
using (var settingsStream = isoStore.OpenFile(filename, FileMode.Open, FileAccess.Read))
{
var serializer = new XmlSerializer(typeof(T));
var settings = (T)serializer.Deserialize(settingsStream);
settingsStream.Close();
return settings;
}
}
}
catch (IOException ioException)
{
errorReporter.WriteError($"IO Exception:{Environment.NewLine}{ioException.Message}");
}
catch (Exception ex)
{
errorReporter.WriteError($"Exception ({ex.GetType().Name}):{Environment.NewLine}{ex.Message}");
}
//no matter what kind of exception,
return new T();
}
public static void SaveSettings(string applicationName, T settings, ISettingsErrorReporter errorReporter)
{
try
{
using (var isoStore = IsolatedStorageFile.GetStore(IsolatedStorageScope.User | IsolatedStorageScope.Domain | IsolatedStorageScope.Assembly, null, null))
{
using (var settingsStream = isoStore.CreateFile(GetFileName(applicationName)))
{
var serializer = new XmlSerializer(typeof(T));
serializer.Serialize(settingsStream, settings);
//let's be safe and use both suspenders and a belt (Flush, Close, Dispose)
settingsStream.Flush();
settingsStream.Close();
}
}
}
catch (IOException ioException)
{
errorReporter.WriteError($"I/O Exception:{Environment.NewLine}{ioException.Message}");
}
catch (Exception ex)
{
errorReporter.WriteError($"Exception ({ex.GetType().Name}):{Environment.NewLine}{ex.Message}");
}
}
private static string GetFileName(string applicationName)
{
return applicationName + ".xml";
}
}
I usually use this from simple WinForms apps. I make my Form class implement this interface:
public interface ISettingsErrorReporter
{
void WriteError(string message);
}
(usually by popping up a message box)
Then I call LoadSettings in the FormLoad event and SaveSettings in the form closing event.
It's not what you are asking, but it may satisfy your needs
Trying to make Android chooser to display available actions for user to launch a PDF file which is stored in my local folder.
When I pass the file name like /data/user/0/myappappname/files/output.pdf , (which exsists, of course), I get a nice chooser with all the apps that can accept a pdf file. But when I pick any of them, I get an error (from external app) The document path is not valid. No exception is thrown.
Then I tried (for testing purposes) to set fname to something like /storage/emulated/0/Download/TLCL.pdf (file also exists), and everything works fine.
At first, I thought that this has something to do with file permissions (since first path is private to my app), but then I found flag ActivityFlags.GrantReadUriPermission built exactly for purpose of temporarily granting file access to other apps. Still same results.
Since this is a Xamarin.forms project, I am limited in choice of file creation locations (I use PCLStorage, which always writes to app-private, local folder), so I don't have an option of generating files in /Documents, /Downloads etc.
I am obviously doing something wrong. Any ideas appreciated.
Is there an option to get full path from system, including the /storage/emulated/0 part (or whatever that would be on other devices)? Maybe that would help?
Piece of code:
(mimeType is defined as "application/pdf" earlier)
public async Task<bool> LaunchFile(string fname, string mimeType)
{
var uri = Android.Net.Uri.Parse("file://" + fname );
var intent = new Intent(Intent.ActionView);
intent.SetDataAndType(uri, mimeType);
intent.SetFlags(ActivityFlags.ClearWhenTaskReset | ActivityFlags.NewTask | ActivityFlags.GrantReadUriPermission );
try
{
Forms.Context.StartActivity(Intent.CreateChooser(intent, "ChooseApp"));
return true;
}
catch (Exception ex)
{
Debug.WriteLine("LaunchFile: " + ex.Message);
return false;
}
My solution to this, which may not be exactly what you want, is to generate a file (in my case a zip file), export it to a public folder, and use that file for the chooser.
Using these:
private readonly string PublicDocsPath = Android.OS.Environment.ExternalStorageDirectory.AbsolutePath + "/AppName";
private readonly string PrivateDocsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData);
and some basic functions:
public Stream GetOutputStream(string destFilePath)
{
string destFolderPath = Path.GetDirectoryName(destFilePath);
if (!Directory.Exists(destFolderPath))
Directory.CreateDirectory(destFolderPath);
return new FileStream(destFilePath, FileMode.Create, FileAccess.Write, FileShare.None);
}
public Stream GetInputStream(string sourceFilePath)
{
if (!File.Exists(sourceFilePath)) throw new FileNotFoundException();
string sourceFolderPath = Path.GetDirectoryName(sourceFilePath);
return new FileStream(sourceFilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
}
You can copy your file to your public folder (or subfolders, you just have to assemble the path) and use that file for your chooser:
public void SendEmail(string subject, string body, string recipient, string mimeType, string attachmentFilePath, string activityTitle)
{
var emailIntent = new Intent(Intent.ActionSendMultiple);
if (string.IsNullOrEmpty(subject)) throw new ArgumentException();
emailIntent.PutExtra(Intent.ExtraSubject, subject);
if (!string.IsNullOrEmpty(recipient))
emailIntent.PutExtra(Intent.ExtraEmail, new[] { recipient });
if (!string.IsNullOrEmpty(body))
emailIntent.PutExtra(Intent.ExtraText, body);
if (!string.IsNullOrEmpty(attachmentFilePath))
{
var file = new Java.IO.File(attachmentFilePath);
file.SetReadable(true, true);
var uri = Android.Net.Uri.FromFile(file);
emailIntent.PutParcelableArrayListExtra(Intent.ExtraStream, new List<IParcelable>(){uri});
}
emailIntent.SetType(mimeType);
_activity.StartActivity(Intent.CreateChooser(emailIntent, activityTitle));
}
This chooser specifically lets the user send their file via email or google drive , but you can assemble it however you want. The attachmentFilePath of this function is the same as the string passed into the GetOutputStream function above.
we're using Acr.IO rather than PCLStorage and I recall that has a property that'll return the fullpath for you.
The code we're using is below, but I wonder if you're simply missing "file://" off the start of your path, as I noticed thats in our code, as well as this previous stackoverflow answer to a similar question as this one, open a PDF in Xamarin.Forms (Android)
We're using a dependency FileService on Android and using this code to open PDFs:
public void OpenNatively(string filePath) {
Android.Net.Uri uri;
if (filePath.StartsWithHTTP()) {
uri = Android.Net.Uri.Parse(filePath);
}
else {
uri = Android.Net.Uri.Parse("file:///" + filePath);
}
Intent intent = new Intent(Intent.ActionView);
var extension = filePath.Substring(filePath.LastIndexOf(".")+1);
if (extension == "ppt" || extension == "pptx") {
extension = "vnd.ms-powerpoint";
}
var docType = "application/" + extension;
intent.SetDataAndType(uri, docType);
intent.SetFlags(ActivityFlags.ClearWhenTaskReset | ActivityFlags.NewTask);
try {
Xamarin.Forms.Forms.Context.StartActivity(intent);
}
catch (Exception e) {
Toast.MakeText(Xamarin.Forms.Forms.Context, "No Application found to view " + extension.ToUpperInvariant() + " files.", ToastLength.Short).Show();
}
}
I need to know if is possible to save the state of a CheckBox in C#? I mean if I check the CheckBox and close the program, once I restart the program the CheckBox will still stay checked. Is it possible to?
This is rather a general question. You need to serialise the state yourself somehow, but how, and where to depends on a lot of things.
Possibly take a look at a Settings file for a simple start.
For this, you will need to record the state of the CheckBox yourself. For example, you could store the value in an XML document that would contain your application's UI states. An example, in a very simplistic form, you could do the following:
// ... as the application is closing ...
// Store the state of the check box
System.IO.File.WriteAllText(#"C:\AppFile.txt", this.CheckBox1.IsChecked.ToString());
// ...
// ... as the application is being initialized ...
// Read the state of the check box
string value = System.IO.File.ReadAllText(#"C:\AppFile.txt");
this.CheckBox1.IsChecked = bool.Parse(value);
As you can see, this simply stores the value in a file and reads it back in during initialization. This is not a great way of doing it, but it demonstrates a possible process to follow.
The easiest way of doing this would be to use a config XML file. You can add this very easily through visual studio, there is no need to use registry and it can be used if the app is portable as the settings are saved with the program. A tutorial of how to set this up is here:
http://www.sorrowman.org/c-sharp-programmer/save-user-settings.html
If you are using Web application cookie enabled and storing the information in cookie then it is possible.
You can checkout http://www.daniweb.com/web-development/aspnet/threads/30505
http://asp.net-tutorials.com/state/cookies/
In C# you can use the Settings file. Information how to use it can be found here : http://msdn.microsoft.com/en-us/library/aa730869%28v=vs.80%29.aspx
If you wanted to save this to the Registry you could do something like this
RegistryKey Regkey = "HKEY_CURRENT_USER\\Software\\MyApplication";
RegKey.SetValue("Checkbox", Checkbox.Checked);
but personally I would save it to the .Config file
Here is an example of how to do it using the Config File if you so desire
private static string getConfigFilePath()
{
return Assembly.GetExecutingAssembly().Location + ".config";
}
private static XmlDocument loadConfigDocument()
{
XmlDocument docx = null;
try
{
docx = new XmlDocument();
docx.Load(getConfigFilePath());
return docx;
}
catch (System.IO.FileNotFoundException e)
{
throw new Exception("No configuration file found.", e);
}
}
private void rem_CheckedChanged(object sender, EventArgs e)
{
if (rem.Checked == true)
{
rem.CheckState = CheckState.Checked;
System.Xml.XmlDocument docx = new System.Xml.XmlDocument();
docx = loadConfigDocument();
System.Xml.XmlNode node;
node = docx.SelectSingleNode("//appsettings");
try
{
string key = "rem.checked";
string value = "true";
XmlElement elem = (XmlElement)node.SelectSingleNode(string.Format("//add[#key='{0}']", key));
if (elem != null)
{
elem.SetAttribute("value", value);
}
else
{
elem = docx.CreateElement("add");
elem.SetAttribute("key", key);
elem.SetAttribute("value", value);
node.AppendChild(elem);
}
docx.Save(getConfigFilePath());
}
catch (Exception e2)
{
MessageBox.Show(e2.Message);
}
}
}
I would use Settings like this:
Assuming a boolean setting called boxChecked has been created.
//if user checks box
Properties.Settings.Default.boxChecked = true;
Properties.Settings.Default.Save();
//...
//when the program loads
if(Properties.Settings.Default.boxChecked)
{
checkBox1.Checked = true;
}
else
{
checkBox1.Checked = false;
}
I'm trying to write my own checking policy.
I want to review if any .cs file contains some code. So my question is, if its possible to get the content of every file from the changeset in the overridden Initialize-Method and/or Evaluate-Method (from PolicyBase).
You can't get the contents from the files directly, you'll need to open them yourselves. For each checked In your Evaluate method, you should look at the PendingCheckin.PendingChanges.CheckedPendingChanges (to ensure that you only limit yourself to the pending changes that will be checked in.) Each PendingChange has a LocalItem that you can open and scan.
For example:
public override PolicyFailure[] Evaluate()
{
List<PolicyFailure> failures = new List<PolicyFailure>();
foreach(PendingChange pc in PendingCheckin.PendingChanges.CheckedPendingChanges)
{
if(pc.LocalItem == null)
{
continue;
}
/* Open the file */
using(FileStream fs = new FileStream(pc.LocalItem, ...))
{
if(/* File contains your prohibited code */)
{
failures.Add(new PolicyFailure(/* Explain the problem */));
}
fs.Close();
}
}
return failures.ToArray();
}