How to use a copy (or same) object in multiple threads - c#

I am trying to make a warning window in an application. The window needs to run on a seperate thread and contains among other things a Canvas depicting a failing object. The Canvas already exists in the main application, and what i need is simply to show the same Canvas in the warning window. The problem is that i get an error saying that another thread owns the object.
I tried doing a deep copy using this method but with no luck. Is there anything i missed, or is there really no simple method to copy a Canvas, or a collection of images. Alternatively, would it be possible to do the deep copy and then change the treading affinity of the copied object?
I should think that someone has encountered this problem before, but my serching skills have given me no relevant results this time.
Thanks in advance!
-ruNury
EDIT 1
private Canvas cloneCanvas()
{
Canvas testcanv = new Canvas();
Dispatcher.Invoke(new Action(delegate
{
var t = SomeViewModel.GetCanvasWithImages();
testcanv = CopyCanvas(t);
}));
return testcanv;
}
public static UIElement DeepCopy(UIElement element)
{
if (element != null)
{
var xaml = XamlWriter.Save(element);
var xamlString = new StringReader(xaml);
var xmlTextReader = new XmlTextReader(xamlString);
var deepCopyObject = (UIElement)XamlReader.Load(xmlTextReader);
return deepCopyObject;
}
return null;
}
private Canvas CopyCanvas(Canvas inputCanvas)
{
if (inputCanvas != null)
{
var outputCanvas = new Canvas();
foreach (UIElement child in inputCanvas.Children)
{
outputCanvas.Children.Add(DeepCopy(child));
}
return outputCanvas;
}
return null;
}

You can singleton pattern to maintain one object of warning window.
When you want to put the canvas into warning window, you have to use Dispatcher.
Dispatcher will marshal method call onto UI thread.
something like
warningWindow.Dispatcher.Invoke(
System.Windows.Threading.DispatcherPriority.Normal,
new Action(
delegate()
{
myCheckBox.IsChecked = true;
}
));
Where warningWindow will be available through singleton instance

Related

How to show a WPF window from Class Library (dll) project?

I have recently made a Class Library (dll) for my other project to program a Bluetooth device via serial port (COM). The library is used to transfer firmware via COM port. It works fine until the requirement comes, which requires a WPF window to show the progress of programming. I have successfully created the progress bar using standard WPF app template. However, the standard WPF does not allow me to generate dll. After searching here, I found this link that teaches you how to add a WPF window to existing Class Library project. Also, someone teaches you how to show the window from here. Everything look good until I tried, there is nothing shows up when I call the method ProgrammBluetooth() from LabVIEW.
My main method, which is in a separate .cs file:
namespace BTMProg
{
public class BTMProgrammer
{
private bool _uut1Status = false;
private string _uut1Message = "";
public bool UUT1Status
{
get { return _uut1Status; }
set { _uut1Status = value; }
}
public string UUT1Message
{
get { return _uut1Message; }
set { _uut1Message = value; }
}
public void ProgramBluetooth (string ioPort, string firmwareFile)
{
List<UUT> uutList = new List<UUT>();
uutList.Add(new UUT(ioPort, "UUT1", 1));
Thread thread = new Thread(() =>
{
var wn = new MainWindow(uutList, firmwareFile);
wn.ShowDialog();
wn.Closed += (s, e) => wn.Dispatcher.InvokeShutdown();
Dispatcher.Run();
if (wn.TaskList[0].Result.ToUpper().Contains("SUCCESS"))
{
_uut1Status = true;
_uut1Message = wn.TaskList[0].Result;
}
else
{
_uut1Status = false;
_uut1Message = wn.TaskList[0].Result;
}
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
}
}
My WPF code in MainWindow.xaml.cs:
ProgrammingViewModel _pvm = new ProgrammingViewModel();
private List<string> _viewModeList = new List<string>();
private List<Task<string>> _taskList = new List<Task<string>>();
public List<Task<string>> TaskList {
get => _taskList;
set => _taskList = value;
}
public MainWindow(List<UUT> uutList, string firmwareFile)
{
InitializeComponent();
foreach (var uut in uutList)
{
_viewModeList.Add(uut.UutName);
}
_pvm.AddProcessViewModels(_viewModeList);
ProgressBarView.DataContext = _pvm.ProcessModels;
StartProgramming(uutList, firmwareFile);
Application.Current.MainWindow.Close();
}
The issue before was that if I don't use dispatcher to create a new thread, an exception saying "The calling thread must be STA, because many UI components require this...." thrown. After I use the new thread, no error but the window does not show up as expected. What could be the problem? Thanks.
The ShowDialog function will stop execution of the thread until the window closes, meaning the rest of that code may not run and the dispatcher may not be started. You should try the Show method instead, which returns as soon as the window is shown.
Also, what is going on with these lines in the constructor of the window?
StartProgramming(uutList, firmwareFile);
Application.Current.MainWindow.Close();
Whatever that first line does, it needs to return and not do a bunch of work if you want the window to finish getting constructed. The second line makes no sense at all. Why are you closing the main window of the application? Did you even set and open a window associated with that property at some point?
I suspect one or more of these things is preventing the thread from ever reaching the point where it can show the window.

Find out which winforms controls are accessed from a background thread

We have built a huge winforms project, already in progress for multiple years.
Sometimes, our users get an exception which looks like this one.
The resolution of this problem seems to be:
don't acces UI components from a background thread
.
But since our project is a very big project with a lot of different threads, we don't succeed in finding all these.
Is there a way to check (with some tool or debugging option) which components are called from a background thread?
To clarify:
I created a sample winforms project with a single Form, containing two Button
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
button1.Text = "Clicked!";
}
private void button2_Click(object sender, EventArgs e)
{
Task.Run(() =>
{
button2.BackColor = Color.Red; //this does not throw an exception
//button2.Text = "Clicked"; //this throws an exception when uncommented
});
}
}
The background color of button2 is set to red when the button is clicked. This happens in a background thread (which is considered bad behavior). However, it doesn't (immediately) throw an exception. I would like a way to detect this as 'bad behavior'. Preferably by scanning my code, but if it's only possible by debugging, (so pausing as soon as a UI component is accessed from a background thread) it's also fine.
I've got 2 recommendations to use together, the first is a Visual Studio Plugin called DebugSingleThread.
You can freeze all the threads and work on one at a time (obviously the non-main-UI threads) and see each threads access to controls. Tedious I know but not so bad with the second method.
The second method is to get the steps in order to reproduce the problem. If you know the steps to reproduce it, it will be easier to see whats causing it. To do this I made this User Action Log project on Github.
It will record every action a user makes, you can read about it here on SO: User Activity Logging, Telemetry (and Variables in Global Exception Handlers).
I'd recommend you also log the Thread ID, then when you have been able to reproduce the problem, go to the end of the log and work out the exact steps. Its not as painful as it seems and its great for getting application telemetry.
You might be able to customise this project, eg trap a DataSource_Completed event or add a dummy DataSource property that sets the real Grids DataSource property and raises an INotifyPropertyChanged event - and if its a non-main thread ID then Debugger.Break();.
My gut feeling is you're changing a control's (eg a grid) data source in a background thread (for that non-freeze feel) and thats causing a problem with synchronisation. This is what happened to the other DevExpress customer who experienced this. Its discussed here in a different thread to the one you referenced.
Is your app set to ignore cross threading intentionally?
Cross-thread operations should be blowing up all the time in winforms. It checks for them like crazy in just about every method. for a starting point check out https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Control.cs.
Somewhere in your app, somebody might have put this line of code:
Control.CheckForIllegalCrossThreadCalls = False;
Comment that out and run the app, then follow the exceptions.
(Usually you can fix the problem by wrapping the update in an invoke, e.g., in a worker thread if you see textbox1.text=SomeString; change it to `textbox.invoke(()=>{textbox1.text=SomeString;});.
You may also have to add checking for InvokeRequired, use BeginInvoke to avoid deadlocks, and return values from invoke, those are all separate topics.
this is assuming even a moderate refactor is out of the question which for even a medium sized enterprise app is almost always the case.
Note: it's not possible to guarantee successful discovery of this case thru static analysis (that is, without running the app). unless you can solve the halting problem ... https://cs.stackexchange.com/questions/63403/is-the-halting-problem-decidable-for-pure-programs-on-an-ideal-computer etc...
I did this to search for that specific situation but of course, need to adjust it to your needs, but the purpose of this is to give you at least a possibility.
I called this method SearchForThreads but since it's just an example, you can call it whatever you want.
The main idea here is perhaps adding this Method call to a base class and call it on the constructor, makes it somewhat more flexible.
Then use reflection to invoke this method on all classes deriving from this base, and throw an exception or something if it finds this situation in any class.
There's one pre req, that is the usage of Framework 4.5.
This version of the framework added the CompilerServices attribute that gives us details about the Method's caller.
The documentation for this is here
With it we can open up the source file and dig into it.
What i did was just search for the situation you specified in your question, using rudimentary text search.
But it can give you an insight about how to do this on your solution, since i know very little about your solution, i can only work with the code you put on your post.
public static void SearchForThreads(
[System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
[System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
[System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
var startKey = "this.Controls.Add(";
var endKey = ")";
List<string> components = new List<string>();
var designerPath = sourceFilePath.Replace(".cs", ".Designer.cs");
if (File.Exists(designerPath))
{
var designerText = File.ReadAllText(designerPath);
var initSearchPos = designerText.IndexOf(startKey) + startKey.Length;
do
{
var endSearchPos = designerText.IndexOf(endKey, initSearchPos);
var componentName = designerText.Substring(initSearchPos, (endSearchPos - initSearchPos));
componentName = componentName.Replace("this.", "");
if (!components.Contains(componentName))
components.Add(componentName);
} while ((initSearchPos = designerText.IndexOf(startKey, initSearchPos) + startKey.Length) > startKey.Length);
}
if (components.Any())
{
var classText = File.ReadAllText(sourceFilePath);
var ThreadPos = classText.IndexOf("Task.Run");
if (ThreadPos > -1)
{
do
{
var endThreadPos = classText.IndexOf("}", ThreadPos);
if (endThreadPos > -1)
{
foreach (var component in components)
{
var search = classText.IndexOf(component, ThreadPos);
if (search > -1 && search < endThreadPos)
{
Console.WriteLine($"Found a call to UI thread component at pos: {search}");
}
}
}
}
while ((ThreadPos = classText.IndexOf("Task.Run", ++ThreadPos)) < classText.Length && ThreadPos > 0);
}
}
}
I hope it helps you out.
You can get the Line number if you split the text so you can output it, but i didn't want to go through the trouble, since i don't know what would work for you.
string[] lines = classText.Replace("\r","").Split('\n');
Try that:
public static void Main(string[] args)
{
// Add the event handler for handling UI thread exceptions to the event.
Application.ThreadException += new ThreadExceptionEventHandler(exception handler);
// Set the unhandled exception mode to force all Windows Forms errors to go through the handler.
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
// Add the event handler for handling non-UI thread exceptions to the event.
AppDomain.CurrentDomain.UnhandledException += // add the handler here
// Runs the application.
Application.Run(new ......);
}
Then you can log the message and the call stack and that should give you enough information to fix the issue.
I recommend you update your GUI to handle this situation automatically for your convenience. You instead use a set of inherited controls.
The general principle here is to override the property Set methods in a way to make them Thread Safe. So, in each overridden property, instead of a straight update of the base control, there's a check to see if an invoke is required (meaning we're on a separate thread the the GUI). Then, the Invoke call updates the property on the GUI thread, instead of the secondary thread.
So, if the inherited controls are used, the form code that is trying to update GUI elements from a secondary thread can be left as is.
Here is the textbox and button ones. You would add more of them as needed and add other properties as needed. Rather than putting code on individual forms.
You don't need to go into the designer, you can instead do a find/replace on the designer files only. For example, in ALL designer.cs files, you would replace System.Windows.Forms.TextBox with ThreadSafeControls.TextBoxBackgroundThread and System.Windows.Forms.Button with ThreadSafeControls.ButtonBackgroundThread.
Other controls can be created with the same principle, based on which control types & properties are being updated from the background thread.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace ThreadSafeControls
{
class TextBoxBackgroundThread : System.Windows.Forms.TextBox
{
public override string Text
{
get
{
return base.Text;
}
set
{
if (this.InvokeRequired)
this.Invoke((MethodInvoker)delegate { base.Text = value; });
else
base.Text = value;
}
}
public override System.Drawing.Color ForeColor
{
get
{
return base.ForeColor;
}
set
{
if (this.InvokeRequired)
this.Invoke((MethodInvoker)delegate { base.ForeColor = value; });
else
base.ForeColor = value;
}
}
public override System.Drawing.Color BackColor
{
get
{
return base.BackColor;
}
set
{
if (this.InvokeRequired)
this.Invoke((MethodInvoker)delegate { base.BackColor = value; });
else
base.BackColor = value;
}
}
}
class ButtonBackgroundThread : System.Windows.Forms.Button
{
public override string Text
{
get
{
return base.Text;
}
set
{
if (this.InvokeRequired)
this.Invoke((MethodInvoker)delegate { base.Text = value; });
else
base.Text = value;
}
}
public override System.Drawing.Color ForeColor
{
get
{
return base.ForeColor;
}
set
{
if (this.InvokeRequired)
this.Invoke((MethodInvoker)delegate { base.ForeColor = value; });
else
base.ForeColor = value;
}
}
public override System.Drawing.Color BackColor
{
get
{
return base.BackColor;
}
set
{
if (this.InvokeRequired)
this.Invoke((MethodInvoker)delegate { base.BackColor = value; });
else
base.BackColor = value;
}
}
}
}

C# WPF ModelVisual3D creation takes too long and cannot be done on separate thread

I have a WPF project (VS2010, .NET4.0) in which I create a rather big ModelVisual3D object (read from custom format STL file, process info, create mesh, etc.) This takes about 3-4 sec. to be created and another 2-3 sec. to do a mainViewport.Children.Add(ModelVisual3D).
I do this all in a custom class and call this method:
class My3DModel
{
...
public MyModelVisual3D createModelVisual3D(MyTypes tType, int tNumber)
{
this.myModelVisual3D = new MyModelVisual3D(tType, tNumber);
for (int i = 0, j = 0; i < this.Triangles.Length; i++)
{
this.mesh.Positions.Add(this.Triangles[i].Vertex1);
this.mesh.Positions.Add(this.Triangles[i].Vertex2);
this.mesh.Positions.Add(this.Triangles[i].Vertex3);
this.mesh.Normals.Add(this.Triangles[i].Normal);
this.mesh.Normals.Add(this.Triangles[i].Normal);
this.mesh.Normals.Add(this.Triangles[i].Normal);
this.mesh.TriangleIndices.Add(j++);
this.mesh.TriangleIndices.Add(j++);
this.mesh.TriangleIndices.Add(j++);
}
this.model3DGroup.Children.Add(new GeometryModel3D(this.mesh, material));
this.myModelVisual3D.Content = this.model3DGroup;
return this.myModelVisual3D;
}
}
The return value is also a custom class I created:
class ToothModelVisual3D : ModelVisual3D
{
//VARIABLES
private MyTypes myType;
private int number;
//OPERATORS
public MyTypes MyType
{get { return myType; } set { myType = value; }}
public int Number
{get { return number; } set { number = value;}}
public ToothModelVisual3D() { }
public ToothModelVisual3D(MyTypes tType, int tNumber) { MyType = tType; Number = tNumber; }
}
All I want to do is the following once in the beginning of the program:
{
My3DModel myModel;
myModel = new My3DModel();
myModel.readFileBytes("C:\\registered\\" + 1 + ".stl");
myModel.loadTriangles();
mainViewport.Children.Add(myModel.createModelVisual3D(MyTypes.Sometype, 1);
}
If I do it on the main thread the UI hangs. If I do it on a worker thread and invoke mainViewport.Children.Add(...) it says it cannot access the resourses created on that worker thread. Help?!
From what I understand I've reached a point where I have two threads and resources belonging to each of them (mainViewport => UIThread & myModel => WorkerThread). Neither thread can access directly the other's resource but creating and using myModel on the UIThread makes it hang... All I want to do is have enough responsiveness from the UI, so the user may minimize the program while waiting for it to load the models, nothing more. How can I do that? Is there a way to do all the CPU heavy work on the UIThread, so no resource conflicts arise and have a worker thread that only handles UI for that time?
PS: I've tried with Thread, BackgroundWorker & Task<TResult> classes. Results were similar if not to say the same.
PPS: The full version will load massive models which will load more than 30-40 sec...
I recently came across the same issue when porting an XNA application to WPF.
In my case I partially resolved this by using a background thread to load the positions, normals, and indices from file. Then in that same thread, construct a memory stream containing XAML for the Model3DGroup with the GeometryModel3D and MeshGeometry3D.
Then, in the UI thread, once the memory stream is available, load the model...
Model3DGroup model = System.Windows.Markup.XamlReader.Load(memoryStream) as Model3DGroup;
There is still a delay, but as file access is done in a background thread, it is not as severe.
Sorry for the late answer, but I actually managed to workaround the problem long time ago the following way:
delegate void myDelegate();
private void fileOpenButton_Click(object sender, RoutedEventArgs e)
{
try
{
Thread ViewportLoaderThread = new Thread(loadViewportItemsAsync);
ViewportLoaderThread.IsBackground = true;
ViewportLoaderThread.Start();
}
catch (Exception err) { UtilsProgram.writeErrorLog(err.ToString()); }
}
private void loadViewportItemsAsync()
{
try
{
//TRY to browse for a file
if (!browseForFile()) return;
Dispatcher.Invoke(new Action(() => { myStatusBar.Visibility = System.Windows.Visibility.Visible; menuItemHelpDemo.IsEnabled = false; }), null);
//Load file, unpack, decrypt, load STLs and create ModelGroup3D objects
UtilsDen.DenModel = new DenLoader(UtilsDen.Filename, UtilsDen.Certificate, UtilsDen.PrivateKey, this);
//Load the models to viewport async
myDelegate asyncDel = new myDelegate(sendModelsToViewportAsync);
this.Dispatcher.BeginInvoke(asyncDel, null);
}
catch (Exception err) { MessageBox.Show(UtilsProgram.langDict["msg18"]); UtilsProgram.writeErrorLog(err.ToString()); }
}
private void sendModelsToViewportAsync()
{
for (int i = 0; i < UtilsDen.DenModel.StlFilesCount; i++)
{
//Add the models to MAIN VIEWPORT
ModelVisual3D modelVisual = new ModelVisual3D();
GeometryModel3D geometryModel = new GeometryModel3D();
Model3DGroup modelGroup = new Model3DGroup();
geometryModel = new GeometryModel3D(UtilsDen.DenModel.StlModels[i].MeshGeometry, UtilsDen.Material);
modelGroup.Children.Add(geometryModel);
modelVisual.Content = modelGroup;
mainViewport.Children.Add(toothModelVisual);
}
}
The key was to use the this.Dispatcher.BeginInvoke(asyncDel, null); as it works on the main thread, but does not lag it, because it is executed asynchronously.
Using a delegate still appears to introduce a lag on the UI, a better solution is create the model in a worker thread and then freeze it. The model can then be cloned by the UI thread without the annoying exception. This works for me with models which take 25 seconds or more to load. The only issue I've found with this is that it doesn't work if the model contains a texture.

Displaying a "User control is loading" message while loading a User Control

I have a Winforms Application with a TabStrip Control. During runtime, UserControls are to be loaded into different tabs dynamically.
I want to present a "User Control xyz is loading" message to the user (setting an existing label to visible and changing its text) before the UserControl is loaded and until the loading is completely finished.
My approaches so far:
Trying to load the User Control in a BackgroundWorker thread. This fails, because I have to access Gui-Controls during the load of the UserControl
Trying to show the message in a BackgroundWorker thread. This obviously fails because the BackgroundWorker thread is not the UI thread ;-)
Show the Message, call DoEvents(), load the UserControl. This leads to different behaviour (flickering, ...) everytime I load a UserControl, and I can not control when and how to set it to invisible again.
To sum it up, I have two questions:
How to ensure the message is visible directly, before loading the User control
How to ensure the message is set to invisible again, just in the moment the UserControl is completely loaded (including all DataBindings, grid formattings, etc.)
what we use is similar to this:
create a new form that has whatever you want to show the user,
implement a static method where you can call this form to be created inside itself, to prevent memory leaks
create a new thread within this form so that form is running in a seperated thread and stays responsive; we use an ajax control that shows a progress bar filling up.
within the method you use to start the thread set its properties to topmost true to ensure it stays on top.
for instance do this in your main form:
loadingForm.ShowLoadingScreen("usercontrollname");
//do something
loadingform.CloseLoadingScreen();
in the loading form class;
public LoadingScreen()
{
InitializeComponent();
}
public static void ShowLoadingScreen(string usercontrollname)
{
// do something with the usercontroll name if desired
if (_LoadingScreenThread == null)
{
_LoadingScreenThread = new Thread(new ThreadStart(DoShowLoadingScreen));
_LoadingScreenThread.IsBackground = true;
_LoadingScreenThread.Start();
}
}
public static void CloseLoadingScreen()
{
if (_ls.InvokeRequired)
{
_ls.Invoke(new MethodInvoker(CloseLoadingScreen));
}
else
{
Application.ExitThread();
_ls.Dispose();
_LoadingScreenThread = null;
}
}
private static void DoShowLoadingScreen()
{
_ls = new LoadingScreen();
_ls.FormBorderStyle = FormBorderStyle.None;
_ls.MinimizeBox = false;
_ls.ControlBox = false;
_ls.MaximizeBox = false;
_ls.TopMost = true;
_ls.StartPosition = FormStartPosition.CenterScreen;
Application.Run(_ls);
}
Try again your second approach:
Trying to show the message in a BackgroundWorker thread. This obviously fails because the BackgroundWorker thread is not the UI thread ;-)
But this time, use the following code in your background thread in order to update your label:
label.Invoke((MethodInvoker) delegate {
label.Text = "User Control xyz is loading";
label.Visible = true;
});
// Load your user control
// ...
label.Invoke((MethodInvoker) delegate {
label.Visible = false;
});
Invoke allows you to update your UI in another thread.
Working from #wterbeek's example, I modified the class for my own purposes:
center it over the loading form
modification of its opacity
sizing it to the parent size
show it as a dialog and block all user interaction
I was required to show a throbber
I received a null error on line:
if (_ls.InvokeRequired)
so I added a _shown condition (if the action completes so fast that the _LoadingScreenThread thread is not even run) to check if the form exists or not.
Also, if the _LoadingScreenThread is not started, Application.Exit will close the main thread.
I thought to post it for it may help someone else. Comments in the code will explain more.
public partial class LoadingScreen : Form {
private static Thread _LoadingScreenThread;
private static LoadingScreen _ls;
//condition required to check if the form has been loaded
private static bool _shown = false;
private static Form _parent;
public LoadingScreen() {
InitializeComponent();
}
//added the parent to the initializer
//CHECKS FOR NULL HAVE NOT BEEN IMPLEMENTED
public static void ShowLoadingScreen(string usercontrollname, Form parent) {
// do something with the usercontroll name if desired
_parent = parent;
if (_LoadingScreenThread == null) {
_LoadingScreenThread = new Thread(new ThreadStart(DoShowLoadingScreen));
_LoadingScreenThread.SetApartmentState(ApartmentState.STA);
_LoadingScreenThread.IsBackground = true;
_LoadingScreenThread.Start();
}
}
public static void CloseLoadingScreen() {
//if the operation is too short, the _ls is not correctly initialized and it throws
//a null error
if (_ls!=null && _ls.InvokeRequired) {
_ls.Invoke(new MethodInvoker(CloseLoadingScreen));
} else {
if (_shown)
{
//if the operation is too short and the thread is not started
//this would close the main thread
_shown = false;
Application.ExitThread();
}
if (_LoadingScreenThread != null)
_LoadingScreenThread.Interrupt();
//this check prevents the appearance of the loader
//or its closing/disposing if shown
//have not found the answer
//if (_ls !=null)
//{
_ls.Close();
_ls.Dispose();
//}
_LoadingScreenThread = null;
}
}
private static void DoShowLoadingScreen() {
_ls = new LoadingScreen();
_ls.FormBorderStyle = FormBorderStyle.None;
_ls.MinimizeBox = false;
_ls.ControlBox = false;
_ls.MaximizeBox = false;
_ls.TopMost = true;
//get the parent size
_ls.Size = _parent.Size;
//get the location of the parent in order to show the form over the
//target form
_ls.Location = _parent.Location;
//in order to use the size and the location specified above
//we need to set the start position to "Manual"
_ls.StartPosition =FormStartPosition.Manual;
//set the opacity
_ls.Opacity = 0.5;
_shown = true;
//Replaced Application.Run with ShowDialog to show as dialog
//Application.Run(_ls);
_ls.ShowDialog();
}
}

Closable tabitem. How to add textbox?

I'm working on a multithreaded application were a new "closable" tab is opened for each new thread. I got the code for closable tabitems from this site but I also want to have a textbox in the tabitem. I tired adding the textbox during runtime from the main method, but it was not accessible from the thread which was created after. what is the best way to make this work? I'm looking for the best way to add a textbox to the closable tabs which I can edit from other worker threads.
EDIT:
I have added some sample code to show what I'm trying to achieve.
namespace SampleTabControl
{
public partial class Window1 : Window
{
public static Window1 myWindow1;
public Window1()
{
myWindow1 = this;
InitializeComponent();
this.AddHandler(CloseableTabItem.CloseTabEvent, new RoutedEventHandler(this.CloseTab));
}
private void CloseTab(object source, RoutedEventArgs args)
{
TabItem tabItem = args.Source as TabItem;
if (tabItem != null)
{
TabControl tabControl = tabItem.Parent as TabControl;
if (tabControl != null)
tabControl.Items.Remove(tabItem);
}
}
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
Worker worker = new Worker();
Thread[] threads = new Thread[1];
for (int i = 0; i < 1; i++)
{
TextBox statusBox = new TextBox();
CloseableTabItem tabItem = new CloseableTabItem();
tabItem.Content = statusBox;
MainTab.Items.Add(tabItem);
int index = i;
threads[i] = new Thread(new ParameterizedThreadStart(worker.start));
threads[i].IsBackground = true;
threads[i].Start(tabItem);
}
}
}
}
And this is the Worker class.
namespace SampleTabControl
{
class Worker
{
public CloseableTabItem tabItem;
public void start(object threadParam)
{
tabItem = (CloseableTabItem)threadParam;
Window1.myWindow1.Dispatcher.BeginInvoke((Action)(() => { tabItem.Header = "TEST"; }), System.Windows.Threading.DispatcherPriority.Normal);
//Window1.myWindow1.Dispatcher.BeginInvoke((Action)(() => { tabItem.statusBox.Text //statusbox is not accesible here= "THIS IS THE TEXT"; }), System.Windows.Threading.DispatcherPriority.Normal);
while (true)
{
Console.Beep();
Thread.Sleep(1000);
}
}
}
}
In the line which I have commented out, statusBox is not accessible.
After seeing your edit, it is clear my original post was not answering the original question.
I think to access the textbox in the way you want you need to cast the tabItem.Content to a Textbox.
Something like below could work
TextBox t = tabItem.Content as TextBox;
if (t != null)
Window1.myWindow1.Dispatcher.BeginInvoke((Action)(() => { t.Text = "THIS IS THE TEXT";}), System.Windows.Threading.DispatcherPriority.Normal);
WPF cannot modify items that were created on a different thread then the current one
If you haven't already, I would highly recommend that you look into the MVVM design pattern. This separates the UI layer from the business logic layer. Your application becomes your ViewModel classes, and the UI layer (Views) are simply a pretty interface that allows users to easily interact with the ViewModels.
This means that all your UI components would share a single thread, while your longer running processes such as retrieving data or crunching numbers can all safely be done on background threads.
For example, you could bind your TabControl.ItemsSource to an ObservableCollection<TabViewModels>, and when you execute the AddTabCommand you would start a new background worker to add a new TabViewModel to the MainViewModel.TabViewModels collection.
Once the background worker finishes it's job. the UI gets automtaically notified that there is a new item in the collection and will draw the new TabItem in the TabControl for you, using whatever DataTemplate you specify.

Categories

Resources