Unity Editor Popup bar error - c#

I've created a custom editor pop up bar. However the selection I pick keeps resetting each time I select a different game object.
Can someone please take a look over my code to see where it keeps getting reset and how I can stop this from happening? The data I input stays there, it's more a convenience thing that I want option I select to stay open when I come back to the editor window.
string[] analytic_Options = {"None", "Button Press"};
private int analytic_Index = 0;
public override void OnInspectorGUI()
{
GUILayout.BeginVertical();
GUILayout.Space(10);
analytic_Index = EditorGUILayout.Popup(analytic_Index, analytic_Options);
UpdateEditorGUI();
GUILayout.Space(10);
GUILayout.EndVertical();
}
void UpdateEditorGUI()
{
switch(analytic_Index)
{
case 0: // No analytics selected
break;
case 1: // Button analytic info
string name_HolderString = EditorGUILayout.TextField("Event Category", target_Object.button_NameString);
if(target_Object.button_NameString != name_HolderString)
{
button_EventNameString = name_HolderString;
target_Object.button_NameString = name_HolderString;
}
break;
}

However the selection I pick keeps resetting each time I select a
different game object.
I guess you are referring to analytic_Index. Everytime your windows (or inspector) is closed, all non serialized properties will be lost. Here an exhaustive description of how serialization works in Unity.
In a few words, for what concern your specific case. Every member variable of Editor class (or EditorWindow) won't be serialized, so it will be lost when the window is closed (or switching to play mode). Generally the SerializedProperties belong to a particular asset (MonoBehavior, ScriptableObject,..), so you can put such a value on the particular asset you are visualizing through your EditorWindow (or Editor).

Related

Outlook Interop add body text

Updated to try and make the question more readable, following comments below
Scenario:
user clicks reply in outlook 365 this opens a mailItem within the main window of outlook. we are attempting to add text to the body of this mailItem.
Currently my method can add text to the to and cc field of the imbedded mailItem but not the body. It will add text to the body, To, Cc, and Subject of a modal mailItem, with this modal open it will even add text to the mailItem's body that it would not work with previously.
When only the main window is open, the focusedClass is not present, so it hits the SendMessage method.
Question:
How do you add text to the body of this mailItem? When only the main Outlook window is open and the MailItem is imbedded in the main window.
So far I have looked at Spy++ and the Z order always changes so I cannot find a logical way to get a child window to work with, plus the structure for the mailItem is different while in the main window and modal.
My current solution uses GetWindowThreadProcessId and GetCurrentThreadId to get the focused window, then the GetFocusClass to find the class, with this class obtained we finally call the below to insert the text:
if (focusedClass.ToLower() == "_wwg")
InsertTextToEmailBody(text);
else
SendMessage(focused, EM_REPLACESEL, 0, newText);
private void InsertTextToEmailBody(string text)
{
var outlookApplication = GetOulookApplication();
var activityInspector = outlookApplication.ActiveInspector();
var currentItem = activityInspector?.CurrentItem;
if (currentItem == null)
{
_log.Error("InsertTextToEmailBody could not find CurrentItem and cannot inject into email body");
return;
}
var myInspector = ((MailItem) currentItem).GetInspector;
var wdDoc = (Document) myInspector.WordEditor;
var currentSelection = wdDoc.Application.Selection;
if (currentSelection.Range?.Text?.Length > 0)
{
//The user has a selected range of text, replace that with transcription
currentSelection.Range.Text = text;
return;
}
// Store the user's current Overtype selection
var userOvertype = wdDoc.Application.Options.Overtype;
// Make sure Overtype is turned off.
if (wdDoc.Application.Options.Overtype) wdDoc.Application.Options.Overtype = false;
// Test to see if selection is an insertion point.
if (currentSelection.Type == WdSelectionType.wdSelectionIP)
currentSelection.TypeText(text);
else if (currentSelection.Type == WdSelectionType.wdSelectionNormal)
{
// Move to start of selection.
if (wdDoc.Application.Options.ReplaceSelection)
{
object direction = WdCollapseDirection.wdCollapseStart;
currentSelection.Collapse(ref direction);
}
currentSelection.TypeText(text);
}
// Restore the user's Overtype selection
wdDoc.Application.Options.Overtype = userOvertype;
}
Not sure why you'd want to use GetFocus() etc. to find the HWND of the Word editor - the focus can be anywhere in the Inspector (e.g. Subject or To edit box), plus clicking on the ribbon can take the focus away from the Word editor even if it was there to begin with.
What works for me is the following:
QI/cast your Inspector object (e.g. retrieved from from Application.ActiveInspector) to IOleWindow. Call IOleWindows.GetWindow() to get the top level HWND of the inspector.
Call EnumChildWindows with a callback that calls GetClassName and compares it with "_WwG". Note that in Office 12 and older the class name is "_WwF".
I also not sure what is wrong with your code that uses Word Object Model to insert text. It looks perfectly fine to me (without actually debugging it), and it would be the preferable solution since it would work out-of-proc since SendMessage(EM_REPLACESEL) requires that both the caller and the target window are in the same process (since it takes a pointer to a text buffer).

How do I save multiple user settings in runtime in C#

I am trying to make my app stay the way I left it after closing the app. Therefore I want to save set of items from ListView to the settings and I can't figure out how to do that. I've found some solutions but I believe they are outdated as they don't seem to work.
Image shows set of items in ListView which I want to save so they appear there after restarting the app:
Items
This is where I want them to appear:
Settings
And this is part of code that I've tried out so far
private void btn_SaveConfig_Click(object sender, EventArgs e)
{
int i = 0;
Settings.Default["IP"] = tBox_discoverServerFrom.Text;
Settings.Default["DiscoveredServers"] = cBox_discoveredServers.Text;
foreach (var item in lV_groups.Items)
{
var property = new System.Configuration.SettingsProperty("Group"+i);
property.PropertyType = typeof(string);
property.Name = "Group " + i;
Settings.Default.Properties.Add(property);
Settings.Default.Save();
i++;
}
}
I do not think using the Settings API is a great idea if you want to save any significant amount of data.
I would recommend the following
Create a class describing all the data you want to save. To make serialization easier it should have a parameter less constructor and public setters for all properties. This is sometimes called a Data-Transfer-Object (DTO)
Use json to serialize the object to a file. You would normally place the file in a subfolder in the local app data folder: Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData).
Do the reverse when you start the application. If there is a file, use Json to deserialize it and use it however you want.
You may optionally add logic to save the file periodically, this would allow recovery in case of application crashes. You might also want some system to keep more than one file, in case the application or computer crashes in the middle of a save operation, and the file becomes corrupt.

The new Input System doesn't trigger anything anymore

This post is shamelessly a copy/paste from my post on the Unity Forums : https://forum.unity.com/threads/input-system-doesnt-trigger-anything-anymore.717386/, but Stack Overflow seems more active
TL;DR : InputSystem worked some days ago, don't trigger anything anymore, halp.
I tried the new Input System some days ago, and that's really neat ! I did a lot of stuff, trying to understand the best way to use it, and, in the end, I had a character jumping and moving everywhere, that was cool ! Then, I merged my code in our develop branch and went to bed.
Today, I want to continue my code, but my character doesn't move anymore, Actions are not triggered (even if inputs are detected in debugger) and I really don't know why. Either the code merge overwrote some important settings (I know what you're thinking and yes, the "Active Input Handling" is set on "Both" and I tried only running the preview) or I did something important during my little tests and I didn't realize.
So I decided to try to reproduce my steps on a fresh new project, maybe you guys can help me figure what do I do wrong ?
1/ Create a new 2D project (via the Hub)
2/ Install the latest Package (version 0.9.0)
3/ Click Yes on that message prompt to activate the new Input management in the settings
4/ Restart Unity Editor since it didn't restart even if the message said it would and check the project settings (yes, it's on "Both", and yes, my Scripting Runtime Version is 4.0)
5/ Create a new GameObject and add a PlayerInput on it
6/ Click on "Open Input Settings" and create an "InputSettings" asset
7/ Click on "Create Actions..." to create my ActionMap asset
8/ Create a "TestAction" on my "Player" ActionMap and set it to the key "t"
9/ Create a new Script "TestScript" that contains a OnTestAction() method (that only logs "test") and enables the test map/action (just to be sure) :
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.PlayerInput;
public class TestScript : MonoBehaviour
{
void Start()
{
InputActionMap playerActionMap = GetComponent<PlayerInput>().actions.GetActionMap("Player");
playerActionMap.Enable();
playerActionMap.GetAction("TestAction").Enable(); //Just to be sure
}
public void OnTestAction()
{
Debug.Log("test");
}
}
10/ Pressing "Play" and spamming "T" like a madman to try to display a debug (note that, in the debugger, a User is created, my "t" presses are detected, my TestAction exists and is mapped on the "t" key but no debug is displayed
It's probably a silly problem, but it's driving me crazy, what do I do wrong ? It's even more infuriating that it worked some days ago !
Additional information :
- Switching the Input Management from "Both" to "New Input System (preview) does nothing
- Checking in Update() is my action is enabled returns "True" every frame
- Checking in Update() is my action is triggered returns "False" every frame
- Using action.started/triggered/performed does nothing (I tried also switching to UnityEvent or C# events for this) :
public class TestScript : MonoBehaviour
{
InputAction a;
void Start()
{
InputActionMap playerActionMap = GetComponent<PlayerInput>().actions.GetActionMap("Player");
playerActionMap.Enable();
a = playerActionMap.GetAction("TestAction");
a.Enable(); //Just to be sure
a.started += OnTriggeredTestAction;
a.performed += OnTriggeredTestAction;
a.canceled += OnTriggeredTestAction;
}
public void OnTestAction()
{
Debug.Log("test");
}
public void OnTriggeredTestAction(InputAction.CallbackContext ctx)
{
Debug.Log("test triggered");
}
}
Injecting directly the InputActionReference of my TestAction and using it does nothing
Forcing "Default Control Scheme" and "Default Action Map" does nothing
Using BroadcastMessage or UnityEvents doesn't work
You probably tried to import a new input system package for multiple input devices compatibility. These types of errors are due to conflict between old and new input system packages and are probably resolved in the latest updates.
To resolve this issue, Go to Edit -> Project Settings->Player->Under Other Settings under Configuration is the option Active Input Handling. Select Both. Unity will restart. Now your problem should be solved. You will be able to use old input system packages and the new ones also simultaneously.
Check for rogue users in the input debugger
I was having very similar symptoms (Input System would randomly just stop sending callbacks). When I opened up the input debugger, it was registering the key presses, but the callbacks were never being called in my script.
Restarting Unity didn't help.
Rebooting didn't help.
I also discovered in the input debugger that there were 2 "users" in the input system and (by process of disabling Game Objects in the scene one at a time) discovered that I had accidentally attached another copy of my Input Action Asset to a different Game Object in the scene and that Unity was registering this other object as a 2nd player or "user", which was assigned all the input action bindings I was trying to capture.
The rogue Action Asset was essentially intercepting the actions, preventing the callbacks from being called on the intended script. I don't know if that's your particular issue, but maybe it will help someone else who (like me) has spent hours pouring through forums, looking for a solution to this elusive problem.
An easy way to tell if you have the same problem is to open the input debugger and see if the desired actions are actually mapped to the user of interest.
Screen clip of input debugger:
For me, there was an unexpected User #1 and only one of the users (not the intended one) actually had keys bound to the desired actions
Posting just incase others run into this issue, as this solved my problem. Make sure to call Enable() for it to start routing events.
//Create a and set the reference
private InputControls _inputMapping;
private void Awake() => _inputMapping = new InputControls();
//Route and Un-route events
private void OnEnable() => _inputMapping.Enable();
private void OnDisable() => _inputMapping.Disable();
I don't know if this will work for you but it worked for me and I was having the same issue.
I had created 2 control schemes. Mobile and Pc. Mobile required touch screen and PC required keyboard and Mouse. Doing this made my Mobile input event stop firing. So adding the Gamepad to my Mobile Control scheme allowed the events to fire again.
TLDR. Check your control scheme make sure it allows for the inputs your binding to.
I had a similar problem, reproduced with exactly the steps described in the question.
In my case, I forgot to set control schemes.
The problem was fixed after adding them.
To do so:
Open your Input Action Asset.
Select a control scheme, in the upper left corner. (say, Keyboard) (if you haven't added a control scheme to begin with, your problem may be different than mine)
Go Right Click > Edit Control Scheme.
EditControlScehme Screen Img
Click on the plus sign to add a control scheme to the list.
Add control scheme to the list Screen Img
Select the control scheme you want to add. (in this case, Keyboard)
Select control scheme Screen Img
Should look like this:
Added control scheme Screen Img
You're all set. Save everything and the problem should be fixed.
Play your game and it should work.
As of at least Unity 2020.1.2 and Input System 1.0.0 the input system will randomly stop working correctly. The only fix I'm aware of is restarting Unity.

Getting the current Directory

I have been working with a simple program essentially designed to be digital flash cards. Ideally, I want the program to be portable. I am trying to get the current directory. My setup is this:
I have a FileIO.cs method which reads currentDir/Data. I then have a winform which calls the method and retrieves a string[] array of the list of folders. Both of these methods are public static. I then have a separate button to create controls on the form.
The problem:
I have cleaned/rebuilt the solution and been using the debugger; The program is running from C:\Users\user\a\b\c\solution\bin\debug. The control (radioButton) I created to verify this data is telling me the 'currentDir' is C:\Users\user and not the programs current directory.
Notes:
I have manually created Debug\Data and 4 folders within the data folder so I am 100% sure they exist. No warnings or compile or run time errors are thrown. Card_Base.GetGrades is automatically called on form load successfully.
I have also confirmed the Release folder is empty and not being used. Also I have a backup program which shows an icon when a file is being uploaded to my backup every time the file(s) change. So I am 100% sure bin\debug is the proper working folder in this scenario.
Perhaps what is puzzling me most is that I have a totally separate program written on the same PC using the same IDE and it properly retrieves the path using the same setup.
In FileIO.cs
//public static string pathPortable = System.IO.Directory.GetCurrentDirectory();
public static string pathPortable = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
static string[] grade1;
public static string[] GetGrade()
{
string fullPath = FileIO.pathPortable + #"\Data";
grade1 = Directory.GetDirectories(fullPath);
return grade1;
}
in Card_Base.cs
public static List<RadioButton> buttonGrade = new List<RadioButton>(10);
public static void GetGrades()
{
string[] grade2 = FileIO.GetGrade();
//Proper and accurate names of the folders I manually added
//C:\Users\user\a\b\c\solution\bin\debug\Data\K
//C:\Users\user\a\b\c\solution\bin\debug\Data\1
//C:\Users\user\a\b\c\solution\bin\debug\Data\2
//C:\Users\user\a\b\c\solution\bin\debug\Data\3
MessageBox.Show("" + grade2[0]); //Information (entire path) is accurate as of this line
int x = 5;
int y = 0;
for (int i = 0; i < grade2.Count(); i++)
{
y = i * 21;
Card_Base.buttonGrade.Add(new RadioButton());
Card_Base.buttonGrade[i].Text = grade2[i];
MessageBox.Show("" + buttonGrade[i].Text); //Proper entire path is shown here!
Card_Base.buttonGrade[i].Location = new System.Drawing.Point(x, y);
}
}
The control whose .text property which shows C:\Users\User, not the Bin\Debug folder.
private void buttonTest_Click(object sender, EventArgs e)
{
MessageBox.Show("abc:" + buttonGrade[0].Text);
for (int i = 0; i < buttonGrade.Count(); i++)
{
panelGrade.Controls.Add(buttonGrade[i]);
}
MessageBox.Show("def:" + buttonGrade[0].Text); //Proper string path is displayed in the popup box here!
}
Result: ???
Four vertically lined radioButtons on a form panel which all have text reading "C:\Users\user\"
Update1:
The text also appears slightly misaligned, slightly higher than the radioButton bubble itself, very strange. However, I've gone back into the editor and confirmed the panelGrade is initially empty. Click/Drag grabs nothing and right clicking the panel does not reveal any underlying objects in the panel's space.
After modifying the target directory to its parent (1 level higher), each messageBox checkpoint reveals the proper string/path is being sent in. The visible radioButton when the program is launcher, after the "test" button is pushed is the only occurrence of this unusual text/string appearing anywhere.
Placing messageBoxes before/after the loop within the test button itself shows that the control (radioButton in buttonGrade[0]) DOES contain the proper string / text / path. Thus the change must occur at some point after the buttonTest code is finished executing.
Update2:
I just opened a brand new solution and copy/pasted the relevant code eliminating anything extraneous. Named all the items/controls with the same names. Completely bare bones. Exact same problem.
HOWEVER, when I change radioButtons to TextBoxs ... the program displays the proper information. Um. What???
Update 3:
Looking through the MSDN radioButtons are derived from buttonBase. The only relevant event I see at a glance is the textChanged event. While it does note that the way the text property of derived classes like radioButton varies, it fails to specify exactly how or what limits it has. Control.Text is simply a System.String , thus I see no reason why a radioButton would not be able to contain that information.
I tried creating a 'test2' button to change 'buttonGrade[0].Text = FileIO.pathPortable;' . Oddly enough, it does not change the text all. Nor does it throw an error.
AH HA! I noticed that the folder after user was my google drive, which is "Google Drive" and has a space in it. I then copied my trash program to C:\and named it TrashMe2 and ensured no folder names contained spaces. The result was "C:\TrashMe2\bi". I then tried "1234567890123456789". The result was that it showed up to the second 3.
The radioButton wasn't receiving a different string and the string/path/data was never changed. It simply showed a 'different folder' because that, by luck of the draw' was the exact number of visible characters it showed. Because I created the radioButtons programmatically, AutoSize did NOT default to true. The proper string was part of the radioButton, it simply was not all visible.
And for security reasons (and a bit of humor), I actually call my user account "user". So it wasn't a scenario where I would see C:\Users\JoeNomidBlow was cut off.
I feel... rather stupid at this particular moment. Thanks for the help!
Programmatically adding a control, particularly a radioButton, does not automatically set the autoSize property to true. When creating a radioButton programatically, one must be sure to add
radioButton.Autosize = true;
or as this case is/was
buttonGrade[i].Autosize = true;
Otherwise the visible space of the text is shortened. In this case, leaving behind a partial path. Which coincidentally happens to be identical to a different valid path.

Prevent an object from being coppied in C# PowerPoint 2010 add-in

I am building a PowerPoint 2010 C# add-in using Visual Studio 2010. One of the functions of the add-in is to add a shape to the current slide. Once the shape is added to the slide though, I need to prevent it from being copied. That is where I am running into issues. I have looked at all the application level events and am not seeing any sort of beforeCopy or beforePaste type of events.
The only option I can think of right now is to add a keydown event listener to listen for "ctrl+c" and block that if my shape is selected and then create a custom right click menu (not even sure if I can yet) to remove the "Copy" option if my shape is selected. There has to be simpler option though.
Anyone have any ideas how I would prevent a user from copying a shape?
The commands executed by built-in ribbon buttons Microsoft Office can be disabled or re-routed. Microsoft calls this "Repurposing", an introduction can be found here.
So another approach could be to "repurpose" the built-in Copy button with something like this. (Needs to be returned by GetCustomUI to customize the ribbon, see the link above.) This modifies the action executed by the Copy button and the callback method that determines whether the button is enabled or not.
<command idMso="Copy" onAction="copyAction" getEnabled="copyEnabled" />
Implement copyAction to return cancelDefault = true when your shape is selected so it will not be copied.
Implement copyEnabled to return false if your shape is selected. Remember to invalidate the button on selection change events.
Actually, one of both approaches should be sufficient. I guess onAction is easier to implement.
Just to close the loop on this, I am sharing my work-around in the hopes that someone else who has this issue will not waste as much time as I have on this. I ended up just using the SlideSelectionChanged and WindowSelectionChange events and a dictionary to delete my objects that have been coppied.
First, when my shape is added to the stage I add a new entry into the dictionary containing the shape name (in my case it was actually a group of shapes) and its ID.
itemIDDictionary.Add(myGroup.Name, myGroup.Id);
WindowSelectionChange is a fairly simple check. It just looks to see if the newly selected item is in the dictionary already. If it is, it then checks to see if the ID matches. If not, it deletes the item. This works because when you copy and paste an item, the newly pasted item is automatically selected on the slide.
public void itemSelectionChange(PowerPoint.Selection SelectedItem)
{
try
{
if (Globals.Ribbons.Ribbon2.itemIDDictionary.ContainsKey(SelectedItem.ShapeRange.Name))
{
for (int shapeIDCount = 0; shapeIDCount < Globals.Ribbons.Ribbon2.itemIDDictionary.Count; shapeIDCount++)
{
if (!Globals.Ribbons.Ribbon2.itemIDDictionary.ContainsValue(SelectedItem.ShapeRange[1].Id))
{
SelectedItem.Delete();
MessageBox.Show("You can not copy the browser object.\nAdd a new one using the ribbon bar");
}
}
}
}
catch {}
SlideSelectionChanged is just a little bit more complicated as I have to loop through all the shapes on the slide.
try
{
if (SldRange.Count > 0)
{
var showWarning = false;
for (int slideCount = 1; slideCount <= SldRange.Count; slideCount++)
{
int shapeCount = 1;
while (shapeCount <= SldRange[slideCount].Shapes.Count)
{
if (Globals.Ribbons.Ribbon2.itemIDDictionary.ContainsKey(SldRange[slideCount].Shapes[shapeCount].Name))
{
if (!Globals.Ribbons.Ribbon2.itemIDDictionary.ContainsValue(SldRange[slideCount].Shapes[shapeCount].Id))
{
SldRange[slideCount].Shapes[shapeCount].Delete();
showWarning = true;
}
else
{
shapeCount++;
}
}
else
{
shapeCount++;
}
}
}
if(showWarning == true)
{
MessageBox.Show("You can not copy the browser object.\nAdd a new one using the ribbon bar");
}
}
}
catch { }
As I said in my initial post, I am sure there is a cleaner way to do this. I just couldn't find one to save my life.

Categories