Async functions with static classes - c#

I have a static class that contains a dictionary of file extensions and BitMapSource objects. That class contains a function that given a FileInfo object will return the associated BitMapSource object, if it's not already in the dictionary, it will get it and put it in the dictionary before returning it.
This works fine when executed from the GUI thread. However, when I try to put it in a background thread I am not getting anything back. Is there any reason that I shouldn't be able to execute this from a background thread?
Static Class
namespace Test.Classes
{
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
public static class IconMap
{
private static Dictionary<string, BitmapSource> iconDictionary = new Dictionary<string, BitmapSource>();
public static BitmapSource GetFileIcon(FileInfo fileInfo)
{
if (iconDictionary.ContainsKey(fileInfo.Extension))
{
return iconDictionary[fileInfo.Extension];
}
else
{
lock (iconDictionary)
{
Icon icon = Icon.ExtractAssociatedIcon(fileInfo.FullName);
BitmapSource bitMapSource = Imaging.CreateBitmapSourceFromHIcon(icon.Handle, new Int32Rect(0, 0, icon.Width, icon.Height), BitmapSizeOptions.FromEmptyOptions());
iconDictionary.Add(fileInfo.Extension, bitMapSource);
return bitMapSource;
}
}
}
}
}
Control.cs
namespace Test.Controls
{
using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Imaging;
using Microsoft.Office.Interop.Outlook;
public partial class AttachedFileInfo : UserControl
{
private FileInfo file;
public AttachedFileInfo(FileInfo fileInfo)
{
this.InitializeComponent();
this.file = fileInfo;
this.FileLink.NavigateUri = new Uri(fileInfo.FullName);
this.FileName.Text = fileInfo.Name;
this.LoadFileIcon(fileInfo);
}
private async void LoadFileIcon(FileInfo fileInfo)
{
Task<BitmapSource> getFileIconTask = Task<BitmapSource>.Factory.StartNew(() =>
{
// If I change this to BitmapSource icon = null; it works as expected.
BitmapSource icon = Classes.IconMap.GetFileIcon(fileInfo);
return icon;
});
await getFileIconTask;
this.FileIcon.Source = Classes.IconMap.GetFileIcon(fileInfo);
// getFileIconTask.Result;
}
private void FileLink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
{
Process.Start(this.file.FullName);
}
}
}

Your icons are not displayed, because WPF resources can only be used from another thread, if they are freezed by calling .Freeze() method (see code bellow).
Another problem is that your GetFileIcon() method is not thread safe even if you use ConcurrentDictionary, bacase it could still happen that one thread adds icon to iconDictionary and when it leaves locked block of code, another thread could enter locked block and add icon for the same file type to the dictionary, resulting in ArgumentException. So within the lock, you must again test if icon for particulatr extension is not already present in dictionary.
private static ConcurrentDictionary<string, BitmapSource> iconDictionary = new ConcurrentDictionary<string, BitmapSource>();
public static BitmapSource GetFileIcon(FileInfo fileInfo)
{
BitmapSource bitMapSource;
if (iconDictionary.TryGetValue(fileInfo.Extension, out bitMapSource))
{
return bitMapSource;
}
else
{
lock (iconDictionary)
{
if (iconDictionary.TryGetValue(fileInfo.Extension, out bitMapSource))
return bitMapSource;
Icon icon = Icon.ExtractAssociatedIcon(fileInfo.FullName);
bitMapSource = Imaging.CreateBitmapSourceFromHIcon(icon.Handle, new Int32Rect(0, 0, icon.Width, icon.Height), BitmapSizeOptions.FromEmptyOptions());
bitMapSource.Freeze();//Allows BitmapSource to be used on another thread
iconDictionary[fileInfo.Extension] = bitMapSource;
return bitMapSource;
}
}
}

Related

C# WPF Changing property of window from other class causes exception

I was trying to create a custom window which changes its theme based on the windows 10 theme (Light or Dark). I was able to listen to the windows 10 theme change at runtime. but when i set my custom dependency property value (WindowTheme), the application throws exception :
Exception thrown: 'System.InvalidOperationException' in
WindowsBase.dll Exception thrown:
'System.Reflection.TargetInvocationException' in mscorlib.dll
Here is my Code:
Window.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Windows.Media;
using UWPHost.Enums;
using UWPHost.Utilities;
using static UWPHost.Utilities.NativeMethods;
namespace UWPHost
{
public class Window:System.Windows.Window
{
public Window()
{
}
private void SetTheme()
{
ResourceDictionary resourceDict = Application.LoadComponent(new Uri("UWPHost;component//Themes/res/Window.xaml", System.UriKind.RelativeOrAbsolute)) as ResourceDictionary;
Application.Current.Resources.MergedDictionaries.Clear();
Application.Current.Resources.MergedDictionaries.Add(resourceDict);
this.Style = (Style)Application.Current.Resources["GenericWindow"];
new ThemeUtility().Init(this);
}
public void SwitchTheme(Theme theme)
{
WindowTheme = theme;
}
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
var hWndSource = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
hWndSource.CompositionTarget.BackgroundColor = (Color)ColorConverter.ConvertFromString(CompositionTargetColor.ToString());
var nonClientArea = new Margins
{
left = ResizeFrameWidth,
top = (int)CaptionHeight,
bottom = ResizeFrameWidth,
right = ResizeFrameWidth
};
DwmExtendFrameIntoClientArea(hWndSource.Handle, ref nonClientArea);
hWndSource.AddHook(new WndProc(this).GetWndProc);
SetWindowPos(hWndSource.Handle, new IntPtr(), 0, 0, 0, 0, 0x0020 | 0x0002 | 0x0001);
SetWindowLong(hWndSource.Handle, GWL_STYLE, GetWindowLong(hWndSource.Handle, GWL_STYLE) & ~WS_SYSMENU);
SetTheme();
}
#region Dependency Properties
public double CaptionHeight
{
get { return (double)GetValue(CaptionHeightProperty); }
set { SetValue(CaptionHeightProperty, value); }
}
public static readonly DependencyProperty CaptionHeightProperty = DependencyProperty.Register("CaptionHeight", typeof(double), typeof(Window), new PropertyMetadata(33.0));
public int ResizeFrameWidth
{
get { return (int)GetValue(ResizeFrameWidthProperty); }
set { SetValue(ResizeFrameWidthProperty, value); }
}
public static readonly DependencyProperty ResizeFrameWidthProperty = DependencyProperty.Register("ResizeFrameWidth", typeof(int), typeof(Window), new PropertyMetadata(10));
public String CompositionTargetColor
{
get { return (String)GetValue(CompositionTargetColorProperty); }
set { SetValue(CompositionTargetColorProperty, value); }
}
public static readonly DependencyProperty CompositionTargetColorProperty = DependencyProperty.Register("CompositionTargetColor", typeof(String), typeof(Window), new PropertyMetadata("#FFFFFF"));
public Theme WindowTheme
{
get { return (Theme)GetValue(WindowThemeProperty); }
set { SetValue(WindowThemeProperty, value); }
}
public static readonly DependencyProperty WindowThemeProperty = DependencyProperty.Register("WindowTheme", typeof(Theme), typeof(Window), new PropertyMetadata(Theme.Default));
#endregion
}
}
ThemeUtility.cs
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Management;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
using UWPHost.Enums;
namespace UWPHost.Utilities
{
public class ThemeUtility
{
private const string ThemeRegistry= #"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize";
private const string RegistryValue= "AppsUseLightTheme";
private readonly string query;
private readonly WindowsIdentity CurrentUser;
ManagementEventWatcher ThemeWatcher;
Window window;
public ThemeUtility()
{
CurrentUser = WindowsIdentity.GetCurrent();
query= string.Format(CultureInfo.InvariantCulture,#"SELECT * FROM RegistryValueChangeEvent WHERE Hive = 'HKEY_USERS' AND KeyPath = '{0}\\{1}' AND ValueName = '{2}'", CurrentUser.User.Value,ThemeRegistry.Replace(#"\", #"\\"),RegistryValue);
}
public void Init(Window window)
{
this.window = window;
InitSystemThemeWatcher();
}
private void InitSystemThemeWatcher()
{
try
{
ThemeWatcher = new ManagementEventWatcher(query);
ThemeWatcher.EventArrived += (sender, args) =>
{
//This code is executed when windows 10 theme changes
if(GetSystemTheme()==Theme.Dark)
{
//Here i'm setting the property of window
window.SwitchTheme(Theme.Dark);
}
else
{
window.SwitchTheme(Theme.Light);
}
};
ThemeWatcher.Start();
}
catch(Exception)
{
throw new Exception("Error Unable to add theme listener.");
}
Theme initialTheme = GetSystemTheme();
}
private static Theme GetSystemTheme()
{
using (RegistryKey key = Registry.CurrentUser.OpenSubKey(ThemeRegistry))
{
object registryValueObject = key?.GetValue(RegistryValue);
if (registryValueObject == null)
{
return Theme.Light;
}
int registryValue = (int)registryValueObject;
return registryValue > 0 ? Theme.Light : Theme.Dark;
}
}
}
}
Exception thrown: 'System.InvalidOperationException' in WindowsBase.dll
From this article i was able to know it can be done using INotifyPropertyChanged, but i dont know how to implement it in my case.
Note:The WindowTheme property is of type enum and have values Light,Dark,Default
The ManagementEventWatcher.EventArrived handler is apparently not called in the UI thread of your application.
A dependency property can only be accessed in the thread in which the owning object was created.
Use the Window's Dispatcher to marshal the execution to the thread where the Window was created, i.e. the UI thread:
public void SwitchTheme(Theme theme)
{
Dispatcher.Invoke(() => WindowTheme = theme);
}

How to modify label text in C# forms

(I am very new to C#) I am creating a forms application, and the purpose is to get a string from a Web API, then put that text onto a label. I have successfully gotten the data from the Web, but when I try to update the label, I have no luck.
I have debugged and found that my method inside my class Is executing, but just not setting the label's text. As you can see below, I tried to use this.resultLabel.text = str;. Here's the classes:
Program.cs (not the form cs file)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Net.Http;
using System.Net;
using System.IO;
namespace WebsiteAPITest
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
class PostManager
{
public void setupClient()
{
HttpWebRequest WebReq = (HttpWebRequest)WebRequest.Create(string.Format("https://yakovliam.com/phpApi/csTest.php"));
WebReq.Method = "GET";
HttpWebResponse WebResp = (HttpWebResponse)WebReq.GetResponse();
string respStr;
using (Stream stream = WebResp.GetResponseStream()) //modified from your code since the using statement disposes the stream automatically when done
{
StreamReader reader = new StreamReader(stream, System.Text.Encoding.UTF8);
respStr = reader.ReadToEnd();
}
MessageBox.Show(respStr);
Form1 form = new Form1();
form.SetResultLabel(respStr);
}
}
}
Actual form class (Form1.cs)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WebsiteAPITest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void GetButton_Click(object sender, EventArgs e)
{
PostManager postManager = new PostManager();
postManager.setupClient();
}
public void SetResultLabel(string str)
{
this.resultLabel.Text = str;
this.resultLabel.Refresh();
}
}
proof of label name:
Inside setupClient you call Form1 form = new Form1(); that creates a second Form1 which you never display, then you call SetResultLabel(respStr) inside this second form you never display, then you leave the method and discard it.
If you want to call SetResultLabel of your calling form, you have to pass the calling form to setupClient:
public void setupClient(Form1 callingForm)
{
...
callingForm.SetResultLabel(respStr);
Then inside your Form1:
postManager.setupClient(this);
It's quite dangerous to pass forms to other methods; a better design is to have the other method return data to your form:
public string setupClient()
{
...
return respStr;
}
And inside Form1:
SetResultLabel(postManager.setupClient());

Change button text from another class in another namespace

I have a problem changing text from another class in another namespace. I have the first Form1 class :
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
static Form1 mainForm;
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool AllocConsole();
public static String LinkToApi = "http://google.com/api/";
public static Comunicator comunicator;
public static int debug = 5;
public Form1()
{
InitializeComponent();
AllocConsole(); // allow console
if(Form1.debug >= 3) Console.WriteLine("Application started");
comunicator = new Comunicator();
mainForm = this;
}
private void TestButton_Click(object sender, EventArgs e)
{
TestButton.Text = "Loading";
comunicator.TestConnection();
}
}
}
and this Comunicator class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Collections.Specialized;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.IO;
using System.Threading;
namespace WindowsFormsApplication1
{
public class Comunicator
{
private String action = "idle";
public static Thread Start(Action action)
{
Thread thread = new Thread(() => { action(); });
thread.Start();
return thread;
}
public Comunicator()
{
}
public void TestConnection()
{
if (Form1.debug >= 3) Console.WriteLine("Testing connection");
// thread test
Start(new Action(ApiTest));
}
public void ApiTest()
{
if (Form1.debug >= 3) Console.WriteLine("API test begin");
// Create a request for the URL.
WebRequest request = WebRequest.Create("http://www.bogotobogo.com/index.php");
// If required by the server, set the credentials.
request.Credentials = CredentialCache.DefaultCredentials;
// Get the response.
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
// Display the status.
Console.WriteLine(response.StatusDescription);
// Get the stream containing content returned by the server.
Stream dataStream = response.GetResponseStream();
// Open the stream using a StreamReader for easy access.
StreamReader reader = new StreamReader(dataStream);
// Read the content.
string responseFromServer = reader.ReadToEnd();
// Display the content.
Console.WriteLine(responseFromServer);
// Cleanup the streams and the response.
reader.Close();
dataStream.Close();
response.Close();
// Console.Read();
if (Form1.debug >= 3) Console.WriteLine("API test end");
// Form1.StaticTestButton.Text = "Loaded"; <---- CHANGE HERE
}
}
}
which is not even a form class (I want to keep everything nice and clean). I want to change the TestButton text into "LOADED" but i get an error when I try to do that as if Form1.TestButton does not exist in Comunicator class.
I have tried to instantiate the class, I made a couple of variables static ... nothing, still getting error.
What is the problem? How may I solve this?
The request must be asynchronous, that's why I am using threads.
You should separate concerns, and you shouldn't communicate with UI in class which is not related to UI.
You should rewrite your code.
But as quick fix you should do the following.
In class Comunicator, you can do such field.
private readonly Action<string> _notifySimpleMessageAction;
Then add to Communicator constructor parameter notifyFunction. Code in constructor:
_notifySimpleMessageAction = notifyFunction
After that you should create Communicator in following manner:
communicator = new Communicator((notification)=>
{
StaticTestButton.BeginInvoke((MethodInvoker)(() => StaticTestButton.AppendText(notification)));
});
Then at the end of your method you should do
_notifySimpleMessageAction("Loaded")
Controller class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ControllerDemonstrator
{
public class Controller
{
public event EventHandler CommunicatorDataLoaded;
public event EventHandler FormTestConnection;
private Form1 _form;
private Communicator _communicator;
public Form1 MainForm
{
get { return _form; }
}
public Controller()
{
_form = new Form1(this);
_form.TestConnection += _form_TestConnection;
_form.FormClosed += _form_FormClosed;
_communicator = new Communicator(this);
_communicator.DataLoaded += _communicator_DataLoaded;
}
public void Start()
{
_form.Show();
}
void _form_FormClosed(object sender, System.Windows.Forms.FormClosedEventArgs e)
{
// put any code to clean up the communicator resources (if needed) here
// --------------------------------------------------------------------
_communicator = null;
// Then exit
// ---------
Application.Exit();
}
private void _communicator_DataLoaded(object sender, EventArgs e)
{
if (null != CommunicatorDataLoaded)
{
CommunicatorDataLoaded(sender, e);
}
}
private void _form_TestConnection(object sender, EventArgs e)
{
if (null != FormTestConnection)
{
FormTestConnection(sender, e);
}
}
}
}
Basic form with one button (_testButton):
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ControllerDemonstrator
{
public partial class Form1 : Form
{
public event EventHandler TestConnection;
public Form1(Controller controller)
{
InitializeComponent();
controller.CommunicatorDataLoaded += controller_CommunicatorDataLoaded;
}
void controller_CommunicatorDataLoaded(object sender, EventArgs e)
{
_testButton.Text = "Loaded";
}
private void _testButton_Click(object sender, EventArgs e)
{
if (null != TestConnection)
{
TestConnection(this, new EventArgs());
}
}
}
}
Communicator class (everything has been stripped out, you will need to add in your logic):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ControllerDemonstrator
{
public class Communicator
{
public event EventHandler DataLoaded;
public Communicator(Controller controller)
{
controller.FormTestConnection += controller_FormTestConnection;
}
private void controller_FormTestConnection(object sender, EventArgs e)
{
// put your code that does the connection here
// -------------------------------------------
if (null != DataLoaded)
{
DataLoaded(this, new EventArgs());
}
}
}
}
And in your Program.cs (assuming that is how you are starting your application):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ControllerDemonstrator
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Controller c = new Controller();
Application.Run(c.MainForm);
}
}
}
With this kind of design, the communicator doesn't know about the form and vice verse. You can expand it out to have different kind's of communicators/forms/etc and have the controller keep track of everything. It is also much easier to test code like this as you can test each separate piece on it's own since they don't depend on each other. This is a quick and dirty implementation. Do some research on the Model View Controller design pattern (not Microsoft MVC for asp.Net, but the actual design pattern). It is more code up-front to code an application with the MVC design pattern but it makes it easier to test and more maintainable.

How to create a Sleep method for my application

I want to create a method which makes my application wait X number of seconds, then continues on down a line of scripts. For example, this is the code that I have so far, after reading many similar help topics:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
methods.WriteTextToScreen(label1, "Hello!");
methods.sleepFor(1);
methods.WriteTextToScreen(label1, "Welcome!");
methods.sleepFor(1);
methods.WriteTextToScreen(label1, "Allo!");
}
public class methods
{
public static int timeSlept;
public static void WriteTextToScreen(Label LabelName, string text)
{
LabelName.Text = text;
}
public static void sleepFor(int seconds)
{
timeSlept = 0;
System.Timers.Timer newTimer = new System.Timers.Timer();
newTimer.Interval = 1000;
newTimer.AutoReset = true;
newTimer.Elapsed += new System.Timers.ElapsedEventHandler(newTimer_Elapsed);
newTimer.Start();
while (timeSlept < seconds)
{
Application.DoEvents();
}
Application.DoEvents();
}
public static void newTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
timeSlept = IncreaseTimerValues(ref timeSlept);
Application.DoEvents();
}
public static int IncreaseTimerValues(ref int x)
{
int returnThis = x + 1;
return returnThis;
}
}
}
}
What I want to do is have my program do the methods.WriteTextToScreen(label1, "Hello!")
then wait for 1 second, then continue on in the same fashion. The problem is that the Form I'm displaying the text on doesn't show up at all until it has written "Allo!" onto the screen, so the first time it appears it already says that. Am I doing something wrong, or is there just no way to do this?
The form doesn't show until it has been constructed i.e. all the code in Form1 is run. See here for info on form constructors: http://msdn.microsoft.com/en-us/library/system.windows.forms.form.form.aspx
To fix your problem you could move the writeTextToScreen and sleep code into the forms on load method. See http://msdn.microsoft.com/en-us/library/system.windows.forms.form.onload.aspx

thread for method with return value in C#

Delay.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace LearnThread
{
class Delay
{
public int Convert()
{
int ErrorCode = 1;
//something
//takes long time. about 9 hours.
return ErrorCode;
}
}
}
Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace LearnThread
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void btnStart_Click(object sender, EventArgs e)
{
Delay delay = new Delay();
Thread t = new Thread(delay.Convert);
//something
MessageBox.Show("Success");
}
}
}
Delay delay = new Delay(); is error here as it is expecting return value. I want the return value as it is contains errorcode. How can I do that? Background worker is better than Thread? Please help. (I should not lose control on the form when delay.Convert() is running.)
As mentioned by Juergen, you can make ErrorCode a class member and then access it once the thread has completed execution. This would require you to create a new instance of the Delay class if you are trying to run multiple Convert in parallel.
You can also use a delegate to get the return value to a variable in the btnStart_Click function as follows:
private void button1_Click(object sender, EventArgs e)
{
Delay delay = new Delay();
int delayResult = 0;
Thread t = new Thread(delegate() { delayResult = delay.Convert(); });
t.Start();
while (t.IsAlive)
{
System.Threading.Thread.Sleep(500);
}
MessageBox.Show(delayResult.ToString());
}
If you plan to run Convert in parallel here, you would have to create as many local variable as required or handle it someother way.
Make the ErrorCode a class member. This way you can get it afterwards.
class Delay
{
public int ErrorCode { get; private set; }
public void Convert()
{
ErrorCode = 1;
...
}
}
private void btnStart_Click(object sender, EventArgs e)
{
Delay delay = new Delay();
Thread t = new Thread(delay.Convert);
//something
int error = delay.ErrorCode;
MessageBox.Show("Success");
}

Categories