Set WPF Checkbox value on window draw (not application start) - c#

I have been searching for this for ages now and still have no idea how to achieve it, this is my first C#/WPF application so it is entirely possible that I have seen the answer and just not known it, apologies if it is really easy.
I have a winForms application that launches a WPF window (the winForms is because the app is a system tray icon that spawns other windows). The only WPF window I have currently has two checkboxes that I have managed to store the value of in app.config, that value is also successfully applied to the checkboxes when the application first starts. However I cannot work out how to have the value applied to the checkbox every time the window is opened.
The window is opened by this (with an attempt to fix it commented out):
private void notifyIcon1_DoubleClick(object Sender, EventArgs e)
{
var RestartPortal = new RestartPortal();
//RestartPortal.InvalidateVisual();
RestartPortal.Show();
}
The values are applied to the window on application run with this (along with another attempt):
public RestartPortal()
{
InitializeComponent();
alwaysOnTopCheck.InvalidateVisual();
closeWhenCompleteCheck.InvalidateVisual();
alwaysOnTopCheck.IsChecked = bool.Parse(ConfigurationManager.AppSettings.Get("onTopChecked"));
closeWhenCompleteCheck.IsChecked = bool.Parse(ConfigurationManager.AppSettings.Get("autoCloseChecked"));
}
If can help out I will be incredibly appreciative.
Thank you.
Edit: In testing I have noticed that the checkboxes seem to be remembering the value that they were assigned when the application first ran, even stranger they force this value into the app.config file each time the window is loaded. To explain better:
Run application
Load Window
Load config in notepad and check that the values correlate
(close notepad) Uncheck checkboxes
Open config file in notepad again to check that values have been saved correctly.
Close window
Open window
Open config file (again in a fresh notepad) and see that the values correlate and that by opening the window the values have been forced back to the values they held when the application was first run.
To my mind the only way that this can be happening is if somehow something is calling the event handlers for the checkboxes as that is the only place that can save to the app.config file. I am now even more confused than I was before, in case it helps here is the XAML for the checkboxes:
<CheckBox x:Name="closeWhenCompleteCheck" Margin="5" HorizontalAlignment="Left" Content="Close when complete" Checked="closeWindow_Checked" Unchecked="closeWindow_Unchecked"/>
<CheckBox x:Name="alwaysOnTopCheck" Margin="5" HorizontalAlignment="Left" Content="Always on top" Checked="onTop_Checked" Unchecked="onTop_Unchecked"/>
And here is the c# for the checked and uncheckeds (though only for one checkbox as they are identical):
private void onTop_Checked(object sender, RoutedEventArgs e)
{
this.Topmost = true;
Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
config.AppSettings.Settings["onTopChecked"].Value = (alwaysOnTopCheck.IsChecked).ToString();
config.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection("AppSettings");
}
private void onTop_Unchecked(object sender, RoutedEventArgs e)
{
this.Topmost = false;
Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
config.AppSettings.Settings["onTopChecked"].Value = (alwaysOnTopCheck.IsChecked).ToString();
config.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection("AppSettings");
}
Any help will be very much appreciated as ever (and probably save my sanity at this point).
Thank you.

You should try utilizing the Loaded event instead of the constructor. If your window closes, but the variable storing it does not get destroyed, your constructor will not be called again.
public RestartPortal()
{
InitializeComponent();
// Subscribe to the Loaded event
this.Loaded += RestartPortal_Loaded;
}
void RestartPortal_Loaded(object sender, RoutedEventArgs e)
{
// Set your values here
alwaysOnTopCheck.IsChecked = bool.Parse(ConfigurationManager.AppSettings.Get("onTopChecked"));
closeWhenCompleteCheck.IsChecked = bool.Parse(ConfigurationManager.AppSettings.Get("autoCloseChecked"));
}

Related

CallContext – value disappears - WPF

I'm using Visual Studio 2015 and .Net
I’ve come upon a somewhat peculiar problem. I have a C# solution with two projects. One is a standard WPF application, the other a WPF User Control library. The WPF application project is the startup project.
In each project I have one window. In the startup project I open the window and set a value in the logical call context by using CallContext.LogicalSetData. This is done on load. I then close the window and open the window in the WPF User Control library. I populate one textbox with the value in the logical call context (using CallContext.LogicalGetData) and this works fine.
I have a button, which on click fires an event that populates another textbox with the same value from the logical call context – but all of a sudden this value is null.
I can make it work simply by changing the starting window to not do it’s “thing” on load but rather on a button event.
The startup window code:
public partial class MainWindow : Window
{
public MainWindow()
{
this.Loaded += OnLoaded;
InitializeComponent();
}
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
CallContext.LogicalSetData("test", "value set onload");
TestWindow win = new TestWindow();
win.Show();
this.Close();
}
private void button_Click(object sender, RoutedEventArgs e)
{
CallContext.LogicalSetData("test", "value set on button event");
TestWindow win = new TestWindow();
win.Show();
this.Close();
}
}
As mentioned above, if I comment out the four lines in the OnLoaded method then it works fine.
The other window:
public partial class TestWindow : Window
{
public TestWindow()
{
InitializeComponent();
PreLoadedText.Text = CallContext.LogicalGetData("test") as string;
}
private void GetValue_Click(object sender, RoutedEventArgs e)
{
string eventTextText = CallContext.LogicalGetData("test") as string;
EventText.Text = eventTextText ?? "The value is null";
}
}
Btw. I tried using the AsyncLocal<T> class - but I just experienced the same problem.
Example solution can be found here:
Visual studio solution as zip file
Note - I'm not looking for a workaround (I have a couple), I'm looking for a reason why this happens.
It's because the instance of Thread.CurrentThread.ExecutionContext, which contains the DataStore of the CallContext, changes between the calls. You can check that if you give it a marker by using "Make Object ID" in the Visual Studio debugger.
Why does this happen? I have absolutely no idea. I tried to debug the .net Framework source without any luck.
As far as AsyncLocal<T> is concerned: It also uses the same Thread.CurrentThread.ExecutionContext and thus suffers from the same problem.
It doesn't happen if you use ThreadLocal<T> because that's using [ThreadStatic] and the Thread itself doesn't change.

How to find cause of WPF Window location change?

Whenever I resume my computer from hibernation, it seems that my Window's location shifts to the top of the screen. I have the following to log my Window location:
LocationChanged += (sender, args) =>
{
Logger.Info($"{Top},{SettingsHelper.Instance.Settings.WindowTop}");
};
WindowTop is a property that is loaded into memory from a file that keeps track of where the Window last was before it was closed. Whenever this prints on startup, both values are correct as expected. However, after resuming from hibernation, this prints a 0 for the actual value, while the setting value is still the same. That eliminates an incorrect setting value being loaded from being the cause.
I'm also attempting to reload the settings whenever the computer resumes from hibernation:
private void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
{
if (e.Mode == PowerModes.Resume)
{
LoadUiSettings();
}
}
private void LoadUiSettings()
{
Left = SettingsHelper.Instance.Settings.WindowLeft;
Top = SettingsHelper.Instance.Settings.WindowTop;
}
This also does not resolve the issue. However, when I add in a MessageBox before the load, it'll show the Window at the incorrect location, then it'll properly load the settings and switch to the correct one. Since I moved my window away from the original location, I can see that on resume, it'll fix the location correctly, then somehow, after unlocking my computer, it'll move itself to Top = 0. Strange thing is that Left is not plagued by this issue at all. All the usages of both properties in the solution are identical. This does not happen with a vanilla WPF application, so I'm not really sure how to investigate this further. Strangely enough, this does not occur if I just lock the computer manually, and not by hibernating.
Is there a way to find out what is changing my Window's Top property somehow?
Edit: my workaround is to use SystemEvents.SessionSwitch and SessionSwitchReason.SessionUnlock and call LoadUiSettings from there, but that's still a bandaid around the actual problem. I'd still like to figure out what's causing it and solve it at the root.
Edit 2: If the Window isn't currently visible, setting it still does not change the actual position. I temporarily overrode the Visibility, then performed the following:
while (Math.Abs(Top - SettingsHelper.Instance.Settings.WindowTop) > 0.1)
{
Top = SettingsHelper.Instance.Settings.WindowTop;
}
I see the Window show up for a split second while it's waiting to be adjusted, then after it disappears again, and I toggle the visibility manually through the UI, it's at the top again. Toggling it manually when it's already in the correct location does not yield this behavior.
Pertinent to your specific question (i.e. finding the CAUSE of the problem, but not asking for the solution), the answer may be found using the following code snippet to perform analysis of WPF Window position change corresponding to PowerModeChange event:
SystemEvents.PowerModeChanged += OnPowerChange;
private void OnPowerChange(object s, PowerModeChangedEventArgs e)
{
switch ( e.Mode )
{
case PowerModes.Resume:
// Log the position as it's done in your example
Logger.Info ("Mode Change: Resume");
Logger.Info($"{Top},{SettingsHelper.Instance.Settings.WindowTop}");
break;
case PowerModes.Suspend:
Logger.Info ("Mode Change: Suspend");
Logger.Info($"{Top},{SettingsHelper.Instance.Settings.WindowTop}");
break;
}
}
Upon finding the root cause of your problem (namely, which event causes the Window dislocation), you may develop relatively simply fix, like for e.g. re-positioning the Window on PowerModes.Resume event.
Hope this may help.

Calling a WPF Window with resourses from the Tray

I have a Windows Tray project that opens a WPF window when Settings is clicked. The WPF window opens and displays some of the content properly, but I have two lists that are bound to another class that have odd behavior.
These lists are displayed on two different tabs as devices. On one tab, there is a graphical representation from which the device can be started, and the other tab shows the settings for the device. Everything works perfectly when the WPF application is set as the startup project. However, when I start it from the tray, the lists load correctly, and display in the first tab, where they can be started, but the second tab shows no devices present. They are both linked to the same data.
At first, I thought that there was an issue with binding, but after several days of trying to resolve this, I believe the problem is with App.xaml, where there is a reference to a resource. I suspect that since i am not referencing App.xaml, the resource is not loaded, and the list is not being set up properly. The only difference between the project working and not working is that one has the WPF as startup project, and the other uses the tray to call the WPF.
My question, then, is how do I reference App.xaml to ensure that I load the required resource.
Below is some of my code, in case it might help.
App.xaml
<Application x:Class="Sender_Receiver.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Shell.xaml">
<Application.Resources>
<ResourceDictionary Source="Themes\Generic.xaml"/>
</Application.Resources>
Current call to open the WPF
private void settingsEvent_Click(object sender, EventArgs e)
{
gui = new Sender_Receiver.mainWindow(); // mainWindow() located in Shell.xaml
gui.Show();
}
Code to display the devices. A collapsibleSection implements Expander and RepeatControl implements ItemsControl.
<c:CollapsibleSection Header="Senders">
<c:CollapsibleSection.CollapsedContent>
<c:RepeatControl Margin="30,0,0,0" ItemsSource="{Binding SendersList}"
ItemType="{x:Type m:Sender}" List="{Binding SendersList}"
ItemTemplate="{StaticResource SenderSummary}"/>
</c:CollapsibleSection.CollapsedContent>
<Border BorderThickness="1" BorderBrush="Chocolate" Margin="30,0,0,0">
<c:RepeatControl ItemsSource="{Binding SendersList}"
ItemType="{x:Type m:Sender}"
List="{Binding SendersList}"
ItemTemplate="{StaticResource SenderTemplate}"/>
</Border>
</c:CollapsibleSection>
The image below shows how the application is behaving under different conditions.
Any assistance would be greatly appreciated.
I finally figured this one out. Instead of instantiating the UI, the entire WPF application must be called to run. This will cause the App.xaml to load the dictionary, and other WPF forms can then access it. This is done with the following code:
private void settingsEvent_Click(object sender, EventArgs e)
{
if (gui == null)
{
gui = new App();
gui.MainWindow = new mainWindow();
gui.InitializeComponent();
}
else
{
gui.InitializeComponent();
gui.MainWindow.Show();
gui.MainWindow = new mainWindow();
}
}
private static App app = new App();
You must keep adding the mainWindow back to the App, as it seems to be set to null when the window shows.
This was discovered through experimentation, so i am sure it is not the best practice, but it works, and right now, that was what I needed.
EDIT
For my purposes, however, this still was not working. I could either open the Settings window only once, or I could not get an event handler to work on it the first time it was open. Finally, Josh came up with the correct answer:
Process myProcess = new Process();
myProcess.StartInfo.UseShellExecute = false;
myProcess.StartInfo.FileName = "C:\\mysettingsapp\\mysettingsapp.exe"; // replace with path to your settings app
myProcess.StartInfo.CreateNoWindow = false;
myProcess.Start();
// the process is started, now wait for it to finish
myProcess.WaitForExit(); // use WaitForExit(int) to establish a timeout
His full explanation can be found here.

WebBrowser control doesn't respond during Form_Load

I have a WinForms form with, amongst other things, a WebBrowser control. I use the browser to display a preview of the file the user creates.
When the user loads a document, I want it to automatically refresh the preview window to show the new document. This works 100%.
However, I just added a "Load most recent document" feature which, as you should be able to tell, loads the last document on program start up. Although it goes through the same code path as any other method of loading a document (Open button, File->Open, File->MRU, etc), the preview does not refresh on start up.
I've followed the execution in the debugger, and all the code is being executed. However, it appears that the WebBrowser simply isn't working. If I hit the refresh button (which goes through the same code path) afterwards, it works fine.
public partial class frmMain : Form {
int scrolltop = 0, scrollleft = 0;
delegate void VoidDelegate();
private void Form1_Load(object sender, EventArgs e) {
//irrelevant initialization code omitted
//this is normally 'about:blank', but it doesn't matter anyway
html.Navigate("http://google.com");
NewFile();
if (GlobalSettings.MRU.Files.Count > 0) {
LoadFile(GlobalSettings.MRU.Files[0]);
}
}
public void NewFile() {
//misc blanking omitted
html.DocumentText = "";
}
private void LoadFile(string file) {
//file loading code omitted
//Trying to call RefreshPreview after everything else is done.
this.Invoke(new VoidDelegate(RefreshPreview));
//RefreshPreview());
}
public void RefreshPreview() {
//preserve the position if possible
if (html.Document.Body != null) {
scrolltop = html.Document.Body.ScrollTop;
scrollleft = html.Document.Body.ScrollLeft;
}
//string code = HtmlProcessing.ProcessCode(txtCode.Text, GetImageList());
string code = "If you can see this, it worked.";
html.DocumentText = code;
}
}
If you paste this code into a form named frmMain with a WebBrowser control named html, and hook up the Form1_Load event (note to self, rename this ;), you should be able to reproduce this sample. Maybe add a button that calls RefreshPreview() too.
So, short version: During Form_Load, WebBrowser doesn't do anything. Afterwards, it works fine. I need it to do something during Form_Load, what am I doing wrong?
I would recommend moving your code to the Form.Shown event. The problem is likely due to the order and timing of Form events. Since Load occurs prior to display of the form, the WebBrowser's VisibleChanged event never occurs, and I believe it is completely inactive.

Retaining the changes made to the backcolor property

I am developing a windows application.
I have 3 forms:
I want to change the backcolor of all the 3 forms to the color selected by the user.
I have used the following code I am able to change the backcolor but When I exit the application and restart it I am not able to get the color that user has set. I am getting the default colour only.
Is it possible to retain the colour selected by the user and use it as backcolor when the user restarts the application.
CODE
In Form1
ColorDialog c1 = new ColorDialog();
public static System.Drawing.Color bkc;
private void button1_Click(object sender, EventArgs e)
{
DialogResult res = c1.ShowDialog();
if (res == DialogResult.OK)
{
bkc = c1.Color;
this.BackColor = bkc;
MessageBox.Show(Convert.ToString(bkc));
}
}
private void button2_Click(object sender, EventArgs e)
{
Form2 obj1 = new Form2();
obj1.BackColor = bkc;
obj1.Show();
}
In Form 2
CODE
private void button2_Click(object sender, EventArgs e)
{
Form3 obj1 = new Form3();
obj1.Show();
}
private void Form2_Load(object sender, EventArgs e)
{
this.BackColor = Form1.bkc;
}
In Form3
CODE
private void button2_Click(object sender, EventArgs e)
{
Form1 obj1 = new Form1();
obj1.Show();
}
private void Form3_Load(object sender, EventArgs e)
{
//Form1 obj2 = new Form1();
this.BackColor = Form1.bkc;
}
In the color dialog box I am selecting a color and pressing Ok button the color is also changed but when I restart the application I dont get the colour which I set using the Color Dialog.I want to retain this setting so that the user can get the desired color without resetting it each time the application is executed.
The above code does not generate any error.
can anybody help me out in performing this task?
Thanks in advance!
You will need to save the value somewhere such as the Application.exe.config:
// Open App.Config of executable
System.Configuration.Configuration config =
ConfigurationManager.OpenExeConfiguration
(ConfigurationUserLevel.None);
// Add an Application Setting.
config.AppSettings.Settings.Add("BackgroundColour",
bkc + " ");
// Save the changes in App.config file.
config.Save(ConfigurationSaveMode.Modified);
// Force a reload of a changed section.
ConfigurationManager.RefreshSection("appSettings");
Here is a C# full code example: Using System.Configuration.ConfigurationManager Example
The suggestion of using the application configuration file is close, but there are two things wrong with it.
First, all users of the application share the same application configuration file. If you have multiple users (on a network, say, or different users on the same machine), storing a user's preference in the application configuration file will change that setting for all users. A second thing wrong with it is that under a default installation on Vista it won't work anyway: by default, Vista doesn't give the user write access to anything under the Program Files directory, so saving changes to the application configuration file will throw an exception.
The right answer is to use user settings. These get stored in the application's user settings file, which lives in a (deeply nested, and OS-version-dependent) subdirectory of the user's home directory. The ConfigurationManager loads these settings at runtime, and lets you update and save them in your code. There's an entire infrastructure built into Visual Studio to make this (relatively) easy, which is good, because doing it properly involves writing a spooky amount of code against the ConfigurationManager class. Here's how it works:
If you look under the Properties of your VS project, you'll see an item called Settings.settings. When you double-click on this, it will show you a grid that lets you add settings to your project. You give the setting name, choose its the data type and default value, and, crucially, the scope. The setting can be application scope, in which case its value will be common to all users of the application and be stored in the application configuration file. Or it can be user scope, in which case each user can have his own value for the setting, and the setting will live in the user settings file.
When you add a setting to this grid, VS generates code to make the setting available to your code. Basically, it creates a class that exposes these settings to your code as properties of a singleton object. (You can see this code if you want to get an idea of what this is saving you from having to do yourself; it gets stored in the 'Settings.Designer.cs' file created under 'Settings.settings' in the project view.) It also, conveniently, regenerates this class every time you change the information in the Settings grid. Once you create a setting in the settings grid, you can reference it in your code thusly:
ctl.BackColor = Properties.Settings.Default.BackColor;
User settings can be modified by your code:
Properties.Settings.Default.BackColor = newBackColor;
And you can save them to the user settings file like this:
Properties.Settings.Default.Save();
Having these settings being exposed as properties of a class is useful for a lot of reasons. One of the most important is that since they're properties (and not, say, dictionary entries accessed by a key, which is how most code that people write against the ConfigurationManager class works), there's compile-time checking of the names you're using in code. You're not ever going to get a NullReferenceException at runtime if you misspell the name of a setting; you'll get an error when you compile it instead.
There are a few subtleties to using user settings. One of the less obvious ones is: what happens when you produce a new release of the software? The user settings are stored in a directory that's keyed to the version number of the program; if you release a new version, the user settings file for it won't exist. How do you keep the user from losing all of his settings when he upgrades your program?
This is also built in to that Settings class; all you need to do is this:
if (Properties.Settings.Default.UpgradeSettings)
{
Properties.Settings.Default.Upgrade();
Properties.Settings.Default.UpgradeSettings = false;
}
This will copy the user's settings from the previous release into the settings file for the new release.
Why dont you create an event that all three forms listen to and get them to change the background colour when listening to the "change colour" event? And you could store the colour in a static variable so that when the form gets loaded, the background colour could be set to that stored in the variable.
In order for the screen to remember the colour settings, why not store the colour selected in a user preferences file? Try the "IsolatedStorage" functionality to save a preferences file.
You are doing it wrong way.
How will the application remember the user choice of backcolor?
The app runs in memory & shows chosen backcolor till its terminated.
Read on this & take it forward.
EDIT: Also, it is not right thing to use Form1.BackColor in Form2.
Open Form1, change backcolor, close Form1 & open Form2 to see what happens (you might see that Form1 opens again).

Categories