Winform with Active UI and 1 Thread running in the background - winforms

I have a Winforms application that calls CorelDraw via it's API and then does some work on an image before saving it. I have tried setting this using Threads, BackgroundWorker, and a class that inherited BackgroundWorker but also allow for Thread.Abort. Every way I have done the work, I continuously run into one of 2 issues. Either the thread is not run in the background, in which case I am unable to press any buttons on the form because it is frozen or the thread runs in the background but immediately stops processing anything when I hit the first call to CorelDraw.
Are there any alternative solutions? The old way we handled this was by having a watcher WinForm spawn another Process() that would do everything in CorelDraw. The process called with Process() saves important information into a .txt file for the parent process to check and if the child works on the same image for too long, the parent calls Kill() on it. So far, this has been a clunky and error prone way to do things, which is why I am looking for a better solution.

Designer Code:
namespace Tasks
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.button2 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(41, 43);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 0;
this.button1.Text = "button1";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// button2
//
this.button2.Location = new System.Drawing.Point(131, 43);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(75, 23);
this.button2.TabIndex = 1;
this.button2.Text = "button2";
this.button2.UseVisualStyleBackColor = true;
this.button2.Click += new System.EventHandler(this.button2_Click);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(240, 116);
this.Controls.Add(this.button2);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Button button2;
}
}
This is the form code:
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Tasks
{
public partial class Form1 : Form
{
CancellationTokenSource cts;
public Form1()
{
InitializeComponent();
}
private void button2_Click(object sender, EventArgs e)
{
MessageBox.Show(this,"This button still works :)");
}
private async void button1_Click(object sender, EventArgs e)
{
cts = new CancellationTokenSource();
await CreateTask();
}
private async Task CreateTask()
{
//Create a progress object that can be used within the task
Progress<string> mProgress; //you can set this to Int for ProgressBar
//Set the Action to a function that will catch the progress sent within the task
Action<string> progressTarget = ReportProgress;
//Your new Progress with the included action function
mProgress = new Progress<string>(progressTarget);
//start your task
string result = await MyProcess(mProgress);
}
private Task<string> MyProcess(IProgress<string> myProgress)
{
return Task.Run(() =>
{
//To report Progress back to your UI thread
myProgress.Report("Starting program now...");
//Start your Corel Draw program here.
Process.Start("C:\\WINDOWS\\system32\\notepad.exe").WaitForExit();
//You can return the Image after your done editing it
return "Program has been closed";
}, cts.Token);
}
private void ReportProgress(string message)
{
//typically to update a progress bar or whatever
MessageBox.Show(this, message);
}
}
}
If you create async task you can run your Corel application in the background and then when it is closed it will be finish the task. I added in extra stuff in there like Progress and cts is used to cancel the task in case the parent window is closed.

Related

WPF Cannot unsubscribe from a RoutedEvent, not working. After unsubscribing it continues firing

I have an WPF User control in which I create a RoutedEventHandler. I want to raise an event notifying every time its height changes:
Wpfusercontrol.designer.cs:
public partial class Wpfusercontrol: System.Windows.Controls.UserControl
{
public static readonly RoutedEvent HeightChangedEvent = EventManager.RegisterRoutedEvent(
"HeightChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Wpfusercontrol));
public event RoutedEventHandler HeightChanged
{
add { AddHandler(HeightChangedEvent, value); }
remove { RemoveHandler(HeightChangedEvent, value); }
}
private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (e.HeightChanged && HeightChangedEvent != null)
{
RaiseEvent(new RoutedEventArgs(HeightChangedEvent));
}
}
}
Then this WPF user control is hosted in an ElementHost
WindowsFormsHostControl.Designer.cs:
partial class WindowsFormsHostControl
{
private void InitializeComponent()
{
this.ElementHostFormControl = new System.Windows.Forms.Integration.ElementHost();
this.Wpfusercontrol= new Wpfusercontrol();
this.SuspendLayout();
//
// ElementHostFormControl
//
this.ElementHostFormControl.Dock = System.Windows.Forms.DockStyle.Fill;
this.ElementHostFormControl.Location = new System.Drawing.Point(0, 0);
this.ElementHostFormControl.Margin = new System.Windows.Forms.Padding(2);
this.ElementHostFormControl.Name = "ElementHostFormControl";
this.ElementHostFormControl.Size = new System.Drawing.Size(75, 78);
this.ElementHostFormControl.TabIndex = 0;
this.ElementHostFormControl.Child = this.Wpfusercontrol;
//
// WindowsFormsHostControl
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.ElementHostFormControl);
this.Margin = new System.Windows.Forms.Padding(2);
this.Name = "WindowsFormsHostControl";
this.Size = new System.Drawing.Size(75, 78);
this.ResumeLayout(false);
}
private System.Windows.Forms.Integration.ElementHost ElementHostFormControl;
private Wpfusercontrol Wpfusercontrol;
}
WindowsFormsHostControl.cs:
public partial class WindowsFormsHostControl: System.Windows.Forms.UserControl
{
private RoutedEventHandler heightChangedEventHandler;
public WindowsFormsHostControl()
{
InitializeComponent();
}
public WindowsFormsHostControl(RoutedEventHandler heightChangedEventHandler) : this()
{
this.heightChangedEventHandler = heightChangedEventHandler;
this.Wpfusercontrol.HeightChanged += this.heightChangedEventHandler;
}
public void SubscribeHeightChanged()
{
this.Wpfusercontrol.HeightChanged += this.heightChangedEventHandler;
}
public void UnsubscribeHeightChanged()
{
this.Wpfusercontrol.HeightChanged -= this.heightChangedEventHandler;
}
}
This WindowsFormsHostControl is embedded within an UI object called custom task pane which is kind of UI container for VSTO Outlook Add-ins. This custom task pane has a button to resize its height but it does not provide an event to catch it. So when you resize the height of that custom task pane, the height of the wpf user control changes as well, so through the routed event in the wpf user control I know when the custom task pane is resized and I catch the event.
Now from one class in my VSTO Outlook Add-in application (which in fact is a winforms app), I perform below things:
private WindowsFormsHostControl windowsFormsHostControl = null;
this.windowsFormsHostControl = new WindowsFormsHostControl(this.WpfUserControl_HeightChanged);
System.Windows.Fomrs.Timer t;
private void WpfUserControl_HeightChanged(object sender, System.Windows.RoutedEventArgs e)
{
// Dome some stuff
...
t = new System.Windows.Fomrs.Timer();
t.Tick += new EventHandler(Update);
t.Interval = 100;
t.Enable = true;
}
private void Update(object sender, EventArgs e)
{
// Some more stuf....
....
// In below lines I update the height of the custom task pane (VSTO Outlook UI object) which in turn causes the WPF user control to resize its height as well. So then, I am trying to unsubscribe from the wpf routed event, then update the height for custom task pane, and finally subscribe again to the wpf routed event. I do this to prevent routed event in wpf user control fires again.
this.windowsFormsHostControl.UnsubscribeHeightChanged();
// here I update the height for custom task pane
this.windowsFormsHostControl.SubscribeHeightChanged();
}
The problem is that it looks like the line:
this.windowsFormsHostControl.UnsubscribeHeightChanged();
is not working because the routed event in the wpf user control continues raising each time I execute the line of code between UnsubscribeHeightChanged and SubscribeHeightChanged.
So what am i doing wrong?

WinForms Button: draw as the default button without setting the form's AcceptButton property (custom drawing, IsDefault property, BS_DEFPUSHBUTTON)

Imagine the following construction in WinForms .NET. A WinForms form contains a custom control with several buttons, which are instances of the traditional Button class. One of these buttons is the default button for the form. The custom control executes the action associated with the default button when ENTER is pressed. This is done in the redefined ProcessCmdKey method:
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (keyData == Keys.Return)
{
buttonOK_Click(null, EventArgs.Empty);
return true;
}
return base.ProcessCmdKey(ref msg, keyData);
}
The default button must have an additional visual cue telling the user that this is the default button (an extra border inside the button). If we did this in a normal form, we would set its AcceptButton property. However, this approach is not applicable here. Even if we find the parent form using the Control.FindForm method or with an expression like (this.Parent as Form), we cannot set the AcceptButton property of the host form and then clear it the right way without resource leak or similar problems (a lot of technical details to place here and to bloat the question).
The first possible way to solve this task is to redefine or enhance the drawing of the button. Is there a relatively easy way to draw a button as the default button with the corresponding visual cue without implementing full custom painting? In my understanding, we might write a special class for our default button based on the following core:
internal class DefaultButton : Button
{
protected override void OnPaint(PaintEventArgs pevent)
{
Rectangle rc = new Rectangle(0, 0, this.Width, this.Height);
ButtonRenderer.DrawButton(pevent.Graphics, rc, System.Windows.Forms.VisualStyles.PushButtonState.Default);
}
}
However, it should take into account the focused state, whether another button on a form is focused (in this case the default button is not drawn with the visual cue), and the like. I could not find a good example of this to use as a basis for my development.
Another possible way to solve my problem could be setting the protected IsDefault property or/and specifying the BS_DEFPUSHBUTTON flag in the overridden CreateParams method in a class inherited from the Button class, for example:
internal class DefaultButton : Button
{
public DefaultButton() : base()
{
IsDefault = true;
}
protected override CreateParams CreateParams
{
get
{
const int BS_DEFPUSHBUTTON = 1;
CreateParams cp = base.CreateParams;
cp.Style |= BS_DEFPUSHBUTTON;
return cp;
}
}
}
But I could not make this code work. Buttons based on this class are always drawn as normal push buttons without the default button visual cue.
I'm not sure about the original requirement; for example I don't have any idea why a UserControl itself should set the AcceptButton of a Form, or what is the expected behavior if there are multiple instances of such controls on the form. It doesn't seem to be responsibility of the UserControl to set the AcceptButton of the Form and there might be better solutions, like relying on events and setting the AcceptButton.
Anyways, the following code example shows you how to set the AcceptButton of a Form; maybe it helps you to find a solutions. The highlights of the code:
The code uses dispose to set the AcceptButton to null.
The code implements ISupportInitialize to set the accept button after initialization of the control is done. If you create the control instance at run-time with code, don't forget to call EndInit, like this: ((System.ComponentModel.ISupportInitialize)(userControl11)).EndInit(); after adding it to the Form, but if you use designer, the designer will take care of that.
The code calls NotifyDefault(true) just for visual effect in design time when it's hosted on a form.
Here's the example:
using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public class UserControl1 : UserControl, ISupportInitialize
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.button2 = new System.Windows.Forms.Button();
this.textBox1 = new System.Windows.Forms.TextBox();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(15, 57);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 0;
this.button1.Text = "button1";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// button2
//
this.button2.Location = new System.Drawing.Point(96, 57);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(75, 23);
this.button2.TabIndex = 1;
this.button2.Text = "button2";
this.button2.UseVisualStyleBackColor = true;
this.button2.Click += new System.EventHandler(this.button2_Click);
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(15, 17);
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(100, 20);
this.textBox1.TabIndex = 2;
//
// UserControl1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.textBox1);
this.Controls.Add(this.button2);
this.Controls.Add(this.button1);
this.Name = "UserControl1";
this.Size = new System.Drawing.Size(236, 106);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.TextBox textBox1;
public System.Windows.Forms.Button button1;
public System.Windows.Forms.Button button2;
public UserControl1()
{
InitializeComponent();
//Just for visual effect in design time when it's hosted on a form
button2.NotifyDefault(true);
}
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show("1");
}
private void button2_Click(object sender, EventArgs e)
{
MessageBox.Show("2");
}
public void BeginInit()
{
}
public void EndInit()
{
var f = this.FindForm();
if (f != null)
f.AcceptButton = button2;
}
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
if (disposing)
{
var f = this.FindForm();
if (f != null)
f.AcceptButton = null;
}
base.Dispose(disposing);
}
}
}

Read in output stream through ssh connection using Renci.SshNet

I've looked at a lot of other stack overflow posts but none of them really were similar to what I am trying to accomplish. Basically, I am trying to connect to a device running Windows CE through an SSH connection and capture any output that is printed to the terminal. When I connect via ssh using Putty I can see many print statements in the terminal which are used for debugging. I am trying to capture these debugging statements and use them in my wpf application. These debugging statements are not a response to a command, they are just printed to the terminal.
So far I am able to send a command and receive a single response but what I am looking for is to be able to receive a response indefinitely, until the user closes the connection or the application.
I am using Renci.SshNet to send my commands and I was messing around with using a ShellStream but was not able to get it working. Here is what I have so far:
using System;
using System.Threading;
using System.Windows;
using Renci.SshNet;
namespace TestSshConsole
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private SshClient _sshConnection;
private ShellStream _shellStream;
private delegate void UpdateTextCallback(string message);
public MainWindow()
{
InitializeComponent();
}
/// <summary>
/// When the user presses connect, connect to the device
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Button_Click(object sender, RoutedEventArgs e)
{
try
{
// Connect to device
_sshConnection = new SshClient(hostname.Text, int.Parse(port.Text), username.Text, password.Text);
_sshConnection.Connect();
// Create a shell stream
_shellStream = _sshConnection.CreateShellStream("test", 80, 60, 800, 600, 65536);
MessageBox.Show("Connected!");
}
catch (Exception exception)
{
MessageBox.Show($"Error {exception.Message}");
}
}
/// <summary>
/// Start a new thread used to receive SSH data when the window is loaded
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Window_Loaded(object sender, RoutedEventArgs e)
{
ThreadStart threadStart = new ThreadStart(RecvSshData);
Thread thread = new Thread(threadStart);
thread.IsBackground = true;
thread.Start();
}
/// <summary>
/// Receive SSH data and write it to the textbox
/// </summary>
private void RecvSshData()
{
while (true)
{
try
{
if (_shellStream != null && _shellStream.DataAvailable)
{
string data = _shellStream.Read();
textBox.Dispatcher.Invoke(new UpdateTextCallback(UpdateText), data);
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
Thread.Sleep(200);
}
}
/// <summary>
/// Write message to the textbox
/// </summary>
/// <param name="message"></param>
private void UpdateText(string message)
{
textBox.AppendText(message + "\r\n");
}
}
}
From what I have read in other posts it seems like this should work and should capture all of the data but it does not. There could be something I am doing wrong in my implementation or they may even be a better way to do it.
Any input with help or recommendations is appreciated.
I got it working to a certain extent. "StartRecording" begins to record the stream on a separate thread which works and just writes it to the console for now. This is able to receive all of the data that is printed to the terminal on my device.
The only issue that I am having now is that the data stops coming through after about a minute. I'm not sure what is happening yet but I think the ShellStream is disconnecting at some point and I'm not sure why.
private SshClient _sshClient;
private ShellStream _shellStream;
private StreamReader _reader;
private StreamWriter _writer;
public Recorder()
{
try
{
_sshClient = new SshClient(_hostname, _port, _username, _password);
_sshClient.Connect();
_shellStream = _sshClient.CreateShellStream("Terminal", 80, 60, 800, 600, 65536);
_reader = new StreamReader(_shellStream, Encoding.UTF8, true, 1024, true);
_writer = new StreamWriter(_shellStream) { AutoFlush = true };
}
catch (Exception e)
{
// TODO
Console.WriteLine(e);
}
}
/// <summary>
/// Begin recording the output of "routediagnostic on" command
/// </summary>
public void StartRecording()
{
try
{
IsRecording = true;
WriteStream("routediagnostic on");
// Start a background thread that will read in the data from the Pyng terminal
ThreadStart threadStart = ReceiveData;
Thread thread = new Thread(threadStart) {IsBackground = true};
thread.Start();
}
catch (Exception e)
{
// TODO
Console.WriteLine(e);
}
finally
{
IsRecording = false;
}
}
private void ReceiveData()
{
while (true)
{
try
{
if (_reader != null)
{
StringBuilder result = new StringBuilder();
string line;
while ((line = _reader.ReadLine()) != null)
{
result.AppendLine(line);
}
if (!string.IsNullOrEmpty(result.ToString()))
{
// TODO - Parse data at this point
Console.WriteLine(result.ToString());
}
}
}
catch (Exception e)
{
// TODO
Console.WriteLine(e);
}
Thread.Sleep(200);
}
}
private void WriteStream(string cmd)
{
_writer.WriteLine(cmd);
while (_shellStream.Length == 0)
{
Thread.Sleep(500);
}
}

How to move borderless child-window/Dialog in WPF

I have a MainWindow with a Border and a ChildWindow as Dialog without a Border. When a child window is open it's not possible to move the mainwindow or to resize it.
I want the application to behave as it is only one Window.
I have tried to use an behavior as in th following link but that is only moving my child window inside of the mainwindow.
DragBahvior
There is a much easier way to enable the dragging, or moving of borderless Windows. Please see the Window.DragMove Method page on MSDN for more details, but in short, you just need to add this line to your code in one of the mouse down event handlers:
public YourWindow()
{
InitializeComponent();
MouseLeftButtonDown += YourWindow_MouseLeftButtonDown;
}
...
private void YourWindow_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DragMove(); // <-- this is all you need to add
}
Users will then be able to click on any area of the Window (dependant upon what you put inside it) and drag it around the screen.
UPDATE >>>
So it seems as though there is more to your requirements than I first noticed. To achieve what you want, there are a number of things that you must do. First, you'll need to position the child Window in a particular place relative to the MainWindow.xaml Window. As you open it, do something like this:
Window window = new Window();
window.Top = this.Top;
window.Left = this.Left;
window.LocationChanged += Window_LocationChanged;
window.ShowDialog();
The child Window position could be offset by some set amount:
Window window = new Window();
window.Top = this.Top + someHorizontalOffsetAmount;
window.Left = this.Left + someVerticalOffsetAmount;
window.LocationChanged += Window_LocationChanged;
window.ShowDialog();
Then you need a handler for the Window.LocationChanged event (which is raised when the child Window is moved):
private void Window_LocationChanged(object sender, EventArgs e)
{
Window window = (Window)sender;
this.Top = window.Top;
this.Left = window.Left;
}
That's it! Now the two Windows will move together. Obviously, if you use an offset in the first example, then you'll need to use the same offset(s) in the Window_LocationChanged handler.
It sounds like your dialog is Modal, ie it was invoked with ShowDialog() and stops you using the rest of the application until it is dismissed, including moving the main window.
If this is not the behaviour you want, then you will need to make your dialog modeless by just calling Show(), or better yet, since you seem to want it to behave as one window, why not use WPF as it was intended and get rid of the dialog altogether?
So I finally found an solution. I wrote an extension to the Windows class and it was quit complicated :)
namespace MultiWindowWPF
{
using System;
using System.Drawing;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using Application = System.Windows.Application;
public static class WindowExtensions
{
/// <summary>
/// Shows the Dialog Modal.
/// </summary>
/// <param name="dialogWindow">The dialog window.</param>
public static void ShowModal(this Window dialogWindow)
{
Window window = Application.Current.Windows.OfType<Window>().FirstOrDefault(w => w.IsKeyboardFocusWithin) ?? Application.Current.MainWindow;
IInputElement lastFocused = FocusManager.GetFocusedElement(window);
IInputElement lastKeyboardSelected = Keyboard.FocusedElement;
EventHandler locationChanged = (sender, args) => ParentWndMove(dialogWindow);
SizeChangedEventHandler sizeChanged = (sender, args) => ParentWndMove(dialogWindow);
EventHandler stateChanged = (sender, args) => ParentWndStateChanged(dialogWindow);
window.LocationChanged += locationChanged;
window.SizeChanged += sizeChanged;
window.StateChanged += stateChanged;
EventHandler close = (sender, args) =>
{
if (dialogWindow.Dispatcher.CheckAccess())
{
dialogWindow.Close();
}
else
{
dialogWindow.Dispatcher.Invoke(dialogWindow.Close);
}
window.LocationChanged -= locationChanged;
window.SizeChanged -= sizeChanged;
window.StateChanged -= stateChanged;
};
EventHandler closed = (sender, args) =>
{
Window self = sender as Window;
Enable();
if (self != null)
{
self.Owner = null;
}
};
ExitEventHandler exit = (sender, args) => close(sender, args);
DependencyPropertyChangedEventHandler isEnabledChanged = null;
isEnabledChanged = (o, eventArgs) =>
{
window.Dispatcher.BeginInvoke(
DispatcherPriority.ApplicationIdle,
new Action(
() =>
{
FocusManager.SetFocusedElement(window, lastFocused);
Keyboard.Focus(lastKeyboardSelected);
}));
((Window)o).IsEnabledChanged -= isEnabledChanged;
};
window.IsEnabledChanged += isEnabledChanged;
dialogWindow.Closed += closed;
Application.Current.Exit += exit;
dialogWindow.Show();
ParentWndMove(dialogWindow);
while (Application.Current != null)
{
DoEvents();
}
}
private static void DoEvents()
{
DispatcherFrame frame = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(ExitFrame), frame);
Dispatcher.PushFrame(frame);
Thread.Sleep(10);
}
private static void Enable()
{
foreach (Window window in Application.Current.Windows.OfType<Window>().Where(window => !window.OwnedWindows.OfType<Window>().Any()))
{
window.IsEnabled = true;
}
}
/// <summary>
/// Exits the frame.
/// </summary>
/// <param name="f">The f.</param>
/// <returns></returns>
public static object ExitFrame(object f)
{
((DispatcherFrame)f).Continue = false;
return null;
}
/// <summary>
/// Parents the WND state changed.
/// </summary>
/// <param name="dialogWindow">The dialog window.</param>
public static void ParentWndStateChanged(Window dialogWindow)
{
Window owner = dialogWindow.Owner;
if (owner.WindowState != WindowState.Maximized)
{
dialogWindow.WindowState = owner.WindowState;
}
ParentWndMove(dialogWindow);
}
/// <summary>
/// Parents the WND move.
/// </summary>
/// <param name="dialogWindow">The dialog window.</param>
public static void ParentWndMove(Window dialogWindow)
{
Window owner = dialogWindow.Owner;
PresentationSource presentationsource = PresentationSource.FromVisual(dialogWindow);
Matrix m = presentationsource.CompositionTarget.TransformToDevice;
double centerWidth = owner.Left + owner.ActualWidth / 2;
double centerHeight = owner.Top + owner.ActualHeight / 2;
if (owner.WindowState == WindowState.Normal)
{
dialogWindow.Top = centerHeight - dialogWindow.ActualHeight / 2;
dialogWindow.Left = centerWidth - dialogWindow.ActualWidth / 2;
}
if (owner.WindowState == WindowState.Maximized)
{
//there is no current main window position to use, center on working screen
Rectangle frame = Screen.FromPoint(new System.Drawing.Point((int)(dialogWindow.Left * m.M11), (int)(dialogWindow.Top * m.M22))).Bounds;
dialogWindow.Left = frame.X / m.M11 + (frame.Width / m.M11 - dialogWindow.ActualWidth) / 2;
dialogWindow.Top = frame.Y / m.M22 + (frame.Height / m.M22 - dialogWindow.ActualHeight) / 2;
}
}
}
}

TreeView in Winforms and focus problem

Can anyone please explain to my why the form in the code below gets out of focus when selecting a treenode in the tree? What should happen is that the form/button should get the focus when the tree disappears like the listview example but it doesn't.
Code example:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace FocusTest
{
public partial class Form1 : Form
{
#region Generated
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.Windows.Forms.ListViewItem listViewItem1 = new System.Windows.Forms.ListViewItem("Item1");
System.Windows.Forms.ListViewItem listViewItem2 = new System.Windows.Forms.ListViewItem("Item2");
System.Windows.Forms.ListViewItem listViewItem3 = new System.Windows.Forms.ListViewItem("Item3");
System.Windows.Forms.TreeNode treeNode1 = new System.Windows.Forms.TreeNode("Node0");
System.Windows.Forms.TreeNode treeNode2 = new System.Windows.Forms.TreeNode("Node1");
System.Windows.Forms.TreeNode treeNode3 = new System.Windows.Forms.TreeNode("Node2");
this.button1 = new System.Windows.Forms.Button();
this.listView1 = new System.Windows.Forms.ListView();
this.button2 = new System.Windows.Forms.Button();
this.treeView1 = new System.Windows.Forms.TreeView();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(12, 12);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 0;
this.button1.Text = "button1";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// listView1
//
this.listView1.Items.AddRange(new System.Windows.Forms.ListViewItem[] {
listViewItem1,
listViewItem2,
listViewItem3
});
this.listView1.Location = new System.Drawing.Point(12, 41);
this.listView1.Name = "listView1";
this.listView1.Size = new System.Drawing.Size(121, 97);
this.listView1.TabIndex = 1;
this.listView1.UseCompatibleStateImageBehavior = false;
this.listView1.Visible = false;
this.listView1.SelectedIndexChanged += new System.EventHandler(this.listView1_SelectedIndexChanged);
this.listView1.View = View.List;
//
// button2
//
this.button2.Location = new System.Drawing.Point(310, 11);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(75, 23);
this.button2.TabIndex = 2;
this.button2.Text = "button2";
this.button2.UseVisualStyleBackColor = true;
this.button2.Click += new System.EventHandler(this.button2_Click);
//
// treeView1
//
this.treeView1.Location = new System.Drawing.Point(310, 41);
this.treeView1.Name = "treeView1";
treeNode1.Name = "Node0";
treeNode1.Text = "Node0";
treeNode2.Name = "Node1";
treeNode2.Text = "Node1";
treeNode3.Name = "Node2";
treeNode3.Text = "Node2";
this.treeView1.Nodes.AddRange(new System.Windows.Forms.TreeNode[] {
treeNode1,
treeNode2,
treeNode3});
this.treeView1.Size = new System.Drawing.Size(121, 97);
this.treeView1.TabIndex = 3;
this.treeView1.Visible = false;
this.treeView1.AfterSelect += new System.Windows.Forms.TreeViewEventHandler(this.treeView1_AfterSelect);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(760, 409);
this.Controls.Add(this.treeView1);
this.Controls.Add(this.button2);
this.Controls.Add(this.listView1);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Button button1;
private System.Windows.Forms.ListView listView1;
private System.Windows.Forms.Button button2;
private System.Windows.Forms.TreeView treeView1;
#endregion
public Form1()
{
InitializeComponent();
}
#region TreeView
private void button2_Click(object sender, EventArgs e)
{
ToggleTreeView();
}
private void ToggleTreeView()
{
if (treeView1.Visible)
{
Controls.Remove(treeView1);
treeView1.Visible = false;
}
else
{
Controls.Add(treeView1);
treeView1.Size = new Size(300, 400);
treeView1.Location = PointToClient(PointToScreen(new System.Drawing.Point(button2.Location.X, button2.Location.Y + button2.Height)));
this.treeView1.BringToFront();
treeView1.Visible = true;
treeView1.Select();
}
}
private void treeView1_AfterSelect(object sender, TreeViewEventArgs e)
{
ToggleTreeView();
}
#endregion
#region ListView
private void button1_Click(object sender, EventArgs e)
{
ToggleListView();
}
private void ToggleListView()
{
if (listView1.Visible)
{
Controls.Remove(listView1);
listView1.Visible = false;
}
else
{
Controls.Add(listView1);
listView1.Size = new Size(300, 400);
listView1.Location = PointToClient(PointToScreen(new System.Drawing.Point(button1.Location.X, button1.Location.Y + button1.Height)));
this.listView1.BringToFront();
listView1.Visible = true;
listView1.Select();
}
}
private void listView1_SelectedIndexChanged(object sender, EventArgs e)
{
if (listView1.Visible)
ToggleListView();
}
#endregion
}
}
Here is the problem and the fix.
TreeView re-grabs focus on Ctrl+Click
I can't explain why the form loses focus, but the problem seems to be the Controls.Remove(treeView1); line. If you remove the Controls.Remove and Controls.Add lines, then it seems to behave better.
Is there a reason why you are removing the treeView from the list of control? Just setting the visibility flag to false will cause the treeView to disappear after the user makes their selection.
EDIT:
In response to your comment:
My guess is that after treeView1_AfterSelect is called, the TreeView is setting focus to itself. Which in your code is impossible because you've removed the control from the form.
To test this theory, I added a timer, replaced the code with:
private void treeView1_AfterSelect(object sender, TreeViewEventArgs e)
{
timer1.Enabled = true;
timer1.Start();
}
private void timer1_Tick(object sender, EventArgs e)
{
ToggleTreeView();
timer1.Stop();
timer1.Enabled = false;
}
Now it works fine. So I'm guessing that the TreeView is calling this.Focus after it has fired the AfterSelect event. (That is just a guess, maybe someone else knows more about the internals them me:) )
And the reason why this is working with the ListView, is that it does not set focus to itself after listView1_SelectedIndexChanged.

Resources