Unity3D: Multiple utility/DisplayModelUtility windows - c#

So I am creating an editor tool, this tool is open by clicking in a custom MenuItem. This tool is open as a utility window using the GetWindow<T>(true, "title") and ShowModalUtility() methods. This new utility window has button that will open another utility window (different editor window type) using the same methods mentioned above.
I don't know if is a bug or by design, but it seems like having multiple utility windows open is not working properly. When the second utility window is open (by pressing the button in the first one) the rest of the unity editor is still block like it should, but the second utility window does not block the first one.
It looks as if being a utility window is a "static" thing and all utility windows have the same priority so you can click on any of them at any time.
Does anyone know how to block one utility editor window using another utility editor window?
P.S: I saw this post, and like I mentioned, I am using ShowModalUtility() but it does not work properly for me, when I have both utility windows open.
UPDATE:
Here is a working example:
using UnityEditor;
using UnityEngine;
public class Test
{
public class TestWindow1 : EditorWindow
{
[MenuItem("TEST/Test Window 1")]
public static void Open()
{
var window = GetWindow<TestWindow1>(true, "Test Window 1");
window.ShowModalUtility();
}
private string text = "";
void OnGUI()
{
text = EditorGUILayout.TextField("Try To Write Here:", text);
if (GUILayout.Button("Open Test Window 2"))
{
TestWindow2.Open();
}
}
}
public class TestWindow2 : EditorWindow
{
public static void Open()
{
var window = GetWindow<TestWindow2>(true, "Test Window 2");
window.ShowModalUtility();
}
private string text = "";
void OnGUI()
{
text = EditorGUILayout.TextField("Try To Write Here:", text);
if (GUILayout.Button("Open Test Window 3"))
{
TestWindow3.Open();
}
}
}
public class TestWindow3 : EditorWindow
{
public static void Open()
{
var window = GetWindow<TestWindow3>(true, "Test Window 3");
window.ShowModalUtility();
}
void OnGUI()
{
EditorGUILayout.LabelField("NOTHING TO DO HERE!!!!");
}
}
}
So if you try to run this code then: You will open TestWindow1, if you try to write something in there it will work and if you try to click on the editor it will be block (this is correct behaviour).
If then you open TestWindow2 you should be able to write in window 2 but you should not be able to write in window 1 (window 1 should be block by window 2) here is the problem: At the moment of writing this update, I am able to write in window 1 even when window 2 is open, and also I can write in both window 1 and 2 when window 3 is open, in other words, the utility windows don't block each other, they only block the editor.

Test environment:
Unity 2021.3.15f1 (SILICON for mac)
In my environment, TestWindow2 is not displayed until TestWindow1 is closed. In my environment, it seems that there is only one Window that can be opened with ShowModalUtility().
I think the easiest way to display multiple windows is to check the flags and pass them to EditorGUI.BeginDisabledGroup. However, in that case, ShowModalUtility() cannot be used.
public class Test
{
public class TestWindow1 : EditorWindow
{
public static bool IsOpen { get; private set; }
[MenuItem("TEST/Test Window 1")]
public static void Open()
{
var window = GetWindow<TestWindow1>(true, "Test Window 1");
window.Show();
}
private string text = "";
void Awake() => IsOpen = true;
void OnGUI()
{
EditorGUI.BeginDisabledGroup(TestWindow2.IsOpen || TestWindow3.IsOpen);
text = EditorGUILayout.TextField("Try To Write Here:", text);
if (GUILayout.Button("Open Test Window 2"))
{
TestWindow2.Open();
}
EditorGUI.EndDisabledGroup();
}
void OnDestroy() => IsOpen = false;
}
public class TestWindow2 : EditorWindow
{
public static bool IsOpen { get; private set; }
public static void Open()
{
var window = GetWindow<TestWindow2>(true, "Test Window 2");
window.Show();
}
private string text = "";
void Awake() => IsOpen = true;
void OnGUI()
{
if (!TestWindow1.IsOpen)
{
Close();
}
EditorGUI.BeginDisabledGroup(TestWindow3.IsOpen);
text = EditorGUILayout.TextField("Try To Write Here:", text);
if (GUILayout.Button("Open Test Window 3"))
{
TestWindow3.Open();
}
EditorGUI.EndDisabledGroup();
}
void OnDestroy() => IsOpen = false;
}
public class TestWindow3 : EditorWindow
{
public static bool IsOpen { get; private set; }
public static void Open()
{
var window = GetWindow<TestWindow3>(true, "Test Window 3");
window.Show();
}
void Awake() => IsOpen = true;
void OnGUI()
{
if (!TestWindow1.IsOpen || !TestWindow2.IsOpen)
{
Close();
}
EditorGUILayout.LabelField("NOTHING TO DO HERE!!!!");
}
void OnDestroy() => IsOpen = false;
}
}
Considering the function of ShowModalUtility(), I do not feel uncomfortable with the specification that only one window can be opened. So I think it is better to use the above method or complete within one Window.
Past Responses: -------------------
Could you please provide a minimum reproduction code? Then I may be able to help.
Below is my test code:
using UnityEditor;
using UnityEngine;
// TestWindow1.cs
public class TestWindow1 : EditorWindow
{
[MenuItem("Test/Window1")]
static void ShowWindow()
{
var window = GetWindow<TestWindow1>();
window.ShowModalUtility();
}
void OnGUI()
{
if (GUILayout.Button("Open Window2"))
{
var window = GetWindow<TestWindow2>();
window.ShowModalUtility();
}
}
}
// TestWindow2.cs
public class TestWindow2 : EditorWindow
{
void OnGUI()
{
if (GUILayout.Button("Open Window3"))
{
var window = GetWindow<TestWindow3>();
window.ShowModalUtility();
}
}
}
// TestWindow3.cs
public class TestWindow3 : EditorWindow {}

Related

Proper best way to only show information/update the main gui

first of all, this may be stupid/dumb question but anyway, this is not a problem, basically im searching for the best proper way to show a result from a class to the main window.
now i will show you what i need, and then how i would solved this with my current knowledge, knowing that its incorrect way,
so on button click i do this:
private void btnSend_Click(object sender, RoutedEventArgs e)
{
new Notification().DoNotify(txtMessage.Text);
}
the classes definitions are:
public class Notification
{
private IMessenger _iMessenger;
public Notification()
{
_iMessenger = new Email();
}
public void DoNotify(string Message)
{
_iMessenger.SendMessage(Message);
}
}
interface IMessenger
{
void SendMessage(string Message);
}
public class SMS : IMessenger
{
public void SendMessage(string Message)
{
// i want code that will print this message variable to the txtSMS textbox.
}
}
public class Email : IMessenger
{
public void SendMessage(string Message)
{
// i want code that will print this message variable to the txtEmail textbox.
}
}
now how to update the main window GUI from these classes?,
one solution that i would use is, add new tempclass with the mainwindow object inside, and then access it,
public class TempClass
{
public static MainWindow Main;
}
in main window constructor:
public MainWindow()
{
InitializeComponent();
TempClass.Main = this;
}
and the sms/email classes:
public class Email : IMessenger
{
public void SendMessage(string Message)
{
TempClass.Main.txtEmail.Text = Message;
}
}
public class SMS : IMessenger
{
public void SendMessage(string Message)
{
TempClass.Main.txtSMS.Text = Message;
}
}
now this works, but there just has to be a better way... what do you think and sorry for long post... ://
is there any principle or design pattern about this ?
A static property is not recommended when you might have two or more instances of your application. You can define a method in MainWindow:
internal void SetMessage(string Message)
{
Dispatcher.BeginInvoke(new Action(() => {txtSMS.Text = Message;}));
}
and send the instance of your MainWindow to other classes, whether by a specific constructor
MainWindow parentWindow;
void btnSend_Click(object sender, RoutedEventArgs e)
{
if (parentWindow != null)
_parentWindow.SetMessage(txtMessage.Text);
}
or by defining a DependencyProperty and use Binding.
You can also use Events, however, I prefer the preceding.

Access statusbar on form from cplex callback function c#

I'm working with an C# .Net application that uses Cplex DLL's for an optimization operation, and during that operation I want to write status progress to a statusbar on the that initiated the operation.
This is the general layout of the specific form;
namespace ActResMain
{
public class FormOptimize : System.Windows.Forms.Form
{
private callCplex()
{
//...
cplex.Use(new Cplex_ContinuousCallback());
cplex.Solve()
}
public void Update_OptimizeStatusbarPanel(String strText)
{
statusBarPanel_1.Text = strText;
statusBar1.Refresh();
}
internal class Cplex_ContinuousCallback : Cplex.ContinuousCallback
{
FormOptimize formOpt = new FormOptimize();
public override void Main()
{
//From here I want to edit the statusbar at FormOptimize. I can write progress to console without any problems, but cannot reach function "Update_OptimizeStatusbarPanel".
//If I include "FormOptimize formOpt = new FormOptimize" here, i get Visual studio exception on illegal window reference.
}
}
}
}
I have also tried invoking the Update_OptimizeStatusbarPanel function like this:
internal class Cplex_ContinuousCallback : Cplex.ContinuousCallback
{
FormOptimize formOpt = new FormOptimize();
public override void Main()
{
FormCollection fc = Application.OpenForms;
var mpc = fc[1];
Type type = mpc.GetType();
MethodInfo dynMethod = type.GetMethod("Update_OptimizeStatusbarPanel");
dynMethod.Invoke(mpc, new object[] { String.Format("Running Optimization: {0} iterations ", Niterations)});
}
}
But then I get an exception from visual studio stating that an object created by one thread cannot be modified from another thread.
Maybe this is something stupid that I have missed, but help is greatly appriciated
EDIT: I edited the code as per Mohammad Dehghans suggestion,
public class FormOptimize : System.Windows.Forms.Form
{
private callCplex()
{
cplex.Use(new Cplex_ContinuousCallback(this));
cplex.Solve()
}
internal class Cplex_ContinuousCallback : Cplex.ContinuousCallback
{
FormOptimize _formOptimize;
public Cplex_ContinuousCallback(FormOptimize formOptimize)
{
this._formOptimize = formOptimize;
}
public override void Main()
{
if (Niterations % 10 == 0)
{
_formOptimize.Update_OptimizeStatusbarPanel(0, String.Format("Running Optimization: {0} iterations ", Niterations), 0);
}
}
}
public void Update_OptimizeStatusbarPanel(short panelIndex, String strText, short severity)
{
if (statusBar1.InvokeRequired)
statusBar1.Invoke(new Action<short, string, short>(Update_OptimizeStatusbarPanel), panelIndex, strText, severity);
else
{
if (panelIndex == 0)
{
//...
statusBarPanel_0.Text = strText;
}
else if (panelIndex == 1)
{
//...
statusBarPanel_1.Text = strText;
}
statusBar1.Refresh();
}
}
}
But by doing that I apparently broke something, as the application just ..stops after statusBar1.Invoke() is called the first time. If I pause the debugger it says that cplex.Solve() is executing, but then nothing more happens.
First of all, you need to pass the instance of your form to the implemented callback class, so when the Main method is called, you have access to the exact instance that is being shown on the screen.
Secondly, you need to use Invoke method to update the UI controls from anther thread (I've not worked with CPLEX so far, but I guess the callback is invoked from another thread. That's usual).
Read this for more information.
The complete code could be:
public class FormOptimize : System.Windows.Forms.Form
{
private callCplex()
{
//Misc code
cplex.Use(new Cplex_ContinuousCallback(this)); // <-- passing `this`
cplex.Solve()
//Misc code
}
public void Update_OptimizeStatusbarPanel(String strText)
{
if (statusBarPanel_1.InvokeRequired)
statusBarPanel_1.Invoke(Action<string>(Update_OptimizeStatusbarPanel), strText);
else
{
statusBarPanel_1.Text = strText;
statusBar1.Refresh();
}
}
internal class Cplex_ContinuousCallback : Cplex.ContinuousCallback
{
FormOptimize _formOptimize;
public Cplex_ContinuousCallback(FormOptimize formOptimize)
{
this._formOptimize = formOptimize;
}
public override void Main()
{
//...
_formOptimize.Update_OptimizeStatusbarPanel(String.Format("Running Optimization: {0} iterations ", Niterations));
}
}
}

How to set focus on "Untitled - Notepad" using windows service

I want to set focus on notepad (Untitled - Notepad) and write some text into it. I have to create a Windows Service for this.
I can create windows service but don't know how to set focus on notepad.
Please provide me code samples in Windows Service
I have tried following code. But no luck with it.
namespace SampleService
{
public partial class Service1 : ServiceBase
{
string application = string.Empty;
public Service1()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
GetTaskWindows();
int iHandle = NativeWin32.FindWindow(null, application);
NativeWin32.SetForegroundWindow(iHandle);
}
protected override void OnStop()
{
GetTaskWindows();
int iHandle = NativeWin32.FindWindow(null, application);
NativeWin32.SetForegroundWindow(iHandle);
}
private void GetTaskWindows()
{
// Get the desktopwindow handle
int nDeshWndHandle = NativeWin32.GetDesktopWindow();
// Get the first child window
int nChildHandle = NativeWin32.GetWindow(nDeshWndHandle, NativeWin32.GW_CHILD);
while (nChildHandle != 0)
{
// Get only visible windows
if (NativeWin32.IsWindowVisible(nChildHandle) != 0)
{
StringBuilder sbTitle = new StringBuilder(1024);
// Read the Title bar text on the windows to put in combobox
NativeWin32.GetWindowText(nChildHandle, sbTitle, sbTitle.Capacity);
String sWinTitle = sbTitle.ToString();
{
if (sWinTitle.Length > 0)
{
if (sWinTitle.Contains("Notepad"))
{
application = sWinTitle;
}
}
}
}
// Look for the next child.
nChildHandle = NativeWin32.GetWindow(nChildHandle, NativeWin32.GW_HWNDNEXT);
}
}
}
}
Fixed myself. Created one small application which will focus on notepad and used sendKeys function to write text into notepad.

C# Cross threading. IRC stream thread to Main UI thread

I've been trying to get this little IRC program working but for some reason I'm having issues with VS and cross threading. I'm not sure if I'm not doing it the proper way or what. Here are the parts causing the issue.
Main Thread:
public partial class MainUI : Form
{
private static IRC irc = new IRC();
public MainUI()
{
InitializeComponent();
}
public static void StartIRC()
{
irc.Start();
}
}
IRC Thread:
class IRC
{
private Thread ircThread;
private bool _running = true;
private NetworkStream stream;
private StreamWriter writer;
private StreamReader reader;
private TcpClient irc;
public IRC(){
ircThread = new Thread(new ThreadStart(Run));
ircThread.IsBackground = true;
}
public void Run(){
while (_running) {
parseInStream(reader.ReadLine());
}
}
public void Start()
{
ircThread.Start();
}
private void parseInStream(String inText)
{
String[] text = inText.Split(' ');
String name;
String message;
if (text[1].Equals("PRIVMSG")) {
name = capsFirstChar(getUser(inText));
message = inText.Substring(inText.IndexOf(":", 1) + 1);
sendToChatBox(capsFirstChar(name) + ": " + message, Color.Black);
}
else if (text[1].Equals("JOIN")) {
name = getUser(inText);
sendToChatBox(capsFirstChar(name) + " has joined the channel.", Color.LimeGreen);
}
else if (text[1].Equals("PART")) {
name = getUser(inText);
sendToChatBox(capsFirstChar(name) + " has left the channel.", Color.Red);
}
}
public void sendToChatBox(String text, Color color)
{
//Trying to send the text to the chatbox on the MainUI
//Works if the MainUI.Designer.cs file has it set to static
if (MainUI.txtMainChat.InvokeRequired) {
MainUI.txtMainChat.Invoke((MethodInvoker)delegate() {
sendToChatBox(text, color);
});
}
else {
MainUI.txtMainChat.SelectionColor = color;
MainUI.txtMainChat.AppendText(text);
}
}
private String getUser(String msg)
{
String[] split = msg.Split('!');
user = split[0].Substring(1);
return capsFirstChar(user);
}
private String capsFirstChar(String text)
{
return char.ToUpper(text[0]) + text.Substring(1).ToLower();
}
}
The only way I am able to get it to work is if I enter the MainUI.Designer.cs file and change the textbox to static and then change everything from this.txtMainChatto MainUI.txtMainChat.
My main problem is that when I make any changes on the visual side all the things labeled static or things named MainUI are deleted. I'm trying to figure out what I need to do to keep this from happening. Am I doing it the right way, or is there a better way? I tried using a background worker but it was using a lot of processing power to work that way for some reason.
I've looked around the web and can't seem to find out how one might relate to my setup. I see people calling a thread from the main thread and then sending things from the main thread to the thread it called but not the other way around. There is nothing else being written to the text box so there won't be an issue with it being used by two threads at the same time.
On my main UI thread I passed in "this" so I could reference the main window from my IRC Class. MainUI.txtMainChat
irc = new IRC(this);
Then in my IRC class
MainUI main;
public IRC(MainUI main){
this.main = main;
ircThread = new Thread(new ThreadStart(Run));
ircThread.IsBackground = true;
}
Then I was able to Change
//MainUI.txtMainChat to
main.txtMainChat
Like Cameron said, Though I know I was told it's not the best approach it gets me started.
Your designer file is rebuilt every time you change your UI in the designer.
You'll need to pass your MainUi to your IRC class, or give it an abstraction of it using an interface (best option).
public interface IMainUI
{
void AddText(string text, Color color);
void UiThread(Action code);
}
public class MainUI : IMainUI
{
// Whatever else
public void AddText(string text, Color color)
{
UiThread( () =>
{
// Same code that was in your Irc.SendToChatBox method.
});
}
public void UiThread(Action code)
{
if (InvokeRequired)
{
BeginInvoke(code);
return;
}
code.Invoke();
}
}
public class IRC
{
IMainUI _mainUi;
//Other properties, and fields
public IRC(IMainUI mainUi)
{
this._mainUi = mainUi;
// Other constructor stuff.
}
// Other logic and methods
}

How do I modify a form's text box from a separate class?

I am trying to create a form where I can update text in a text box without requiring interaction from the user. Right now I am trying to create a method in my form's class that updates the TextBox.Text field. Outside of the class I am not able to access the function.
Right now I am trying
static void Main()
{
Form status = new Window();
Application.Run(status);
status.UpdateTextBox("NewText");
}
My form class looks like (from the designer)
public partial class Window : Form
{
public Window()
{
InitializeComponent();
}
public void UpdateTextBox(string text)
{
textBox1.Text = text;
}
}
I have also tried making the Textbox a public property like this.
public string DisplayedText
{
get
{
return textbox1.Text;
}
set
{
textbox1.Text = value;
}
}
I am trying to edit the field during runtime. Is this possible?
You can access the function, but if you look at the code, there is a problem:
static void Main()
{
Form status = new Window();
Application.Run(status); // Blocks here!
status.UpdateTextBox("NewText");
}
When you call Application.Run(), it will not return control to your program until the status form has closed. At that point, setting the status is too late...
You can set it before you run, but after you've constructed:
static void Main()
{
Form status = new Window();
status.UpdateTextBox("NewText");
Application.Run(status);
}
It's this code...the Application.Run will block, status.UpdateTextBox isn't going to execute until you close the form.
static void Main()
{
Form status = new Window();
Application.Run(status);
status.UpdateTextBox("NewText");
}

Categories

Resources