C# TreeView sometimes refuse to paint itself after invalidates - c#

I use a TreeView to display some informations in two levels :
A
B
1
D
1
2
...
Sometimes, the informations stored in the treeview differs with the one displayed. It seems that it's because Paint() is not called after Invalidates().
I already tried answer from this question : C# Treeview doesn't refresh after moving nodes, without success.
Tree (re)Creation code :
using System.Windows.Forms.TreeNode;
using System.Windows.Forms.TreeView;
[...]
private void createTree()
{
[...]// Creation code
// Check update of the treeview
foreach (TreeNode n in viewDataTreeView.Nodes)
{
Console.WriteLine(n.Name);
foreach (TreeNode child in n.Nodes)
{
Console.WriteLine(" " + child.Name);
}
}
Console.WriteLine("done");
this.Invalidate(true);
}
Which always output the correct tree that I have in the treeview. And sometimes, newly added node are not displayed on the screen.
Working case:
callstack:
Functions of working callstack :
private void toolStripDeleteTemplateButton_Click(object sender, EventArgs e)
{
//Some confirmation stuff
[...]
// Delete the template file
GraphTemplateNode node = this.viewDataTreeView.SelectedNode as GraphTemplateNode;
File.Delete(node.GetTemplateFilePath());
createTree();
}
Not working case:
callstack:
See the Test 4 is missing.
Functions of unworking callstack :
//LineGraphUIControl.cs
private void saveTemplateToolStripButton_Click(object sender, EventArgs e)
{
base.SaveGraphTemplate(lineGraphControl1.Graph);
}
//GraphUIControl.cs
public void SaveGraphTemplate(Graph graph)
{
//Getting file name
[...]
//Creating template
ViewDataSubControl.AddNewUserTemplate(tmplt);
}
// ViewDataSubControl.cs
public void AddNewUserTemplate(GraphTemplate tmplt)
{
//Some string calculations
[...]
tmplt.SaveTemplate(fullName);
createTree();
}
I tried to use the method Refresh(), Update() and BeginUpdate() & EndUpdate() with no luck. The event Invalidated is always fired, but I can't get Paint() to be called everytime. If I force call with InvokePaint() the TreeView is not updated either.
What can I do to make it works ?

That issue is unusual, and I suspect that it is something wrong in the logic that renders the TreeView. You wrote in a comment that the code is long, you did not create it and you don't want to copy/paste it.
I understand this.
In general, there are many reasons why it could happen:
It might be a missing EndUpdate at some point. Check the code that runs when you delete and re-add a node.
Node display customization can be troublesome. Check any code that could have an impact on node rendering (the DrawNode event for example)
Check exception handlers. An exception could happens and break a code flow. Remove empty catch sections if it applies.
It might be a misuse of a trick like this one that disable Redraws on demand to speed up the display. In this last case, a missing ResumeDrawing could lock it the same way.
Unfortunately it is very hard to guess what is wrong without the whole code... So the best I can do to help is to give some advices to check what it is going on:
At first, comment every BeginUpdate, EndUpdate, SuspendDrawing, ResumeDrawing together, and check what happens.
If the bug is still here, have a code that populates the TreeView as simple as possible by commenting what is not related to it, Disable events that are used to customize the node display like the DrawNode event (if it applies)
...until it works as expected.
Then, uncomment pieces of codes to re-enable existing features, one by one, until you bump on the issue or notice what is wrong by looking at the code. You will isolate the bug this way.
Hope it helps at least a bit.

Related

Hide and show a button in Unity (C#)

I have a button (in UI), that I want to hide sometimes (but show again later), which means that it shouldn't show anymore and I shouldn't be able to click it. The only solution I found (that has actually managed to hide the button), is SetActive().
But in Update(), when I do SetActive(false) (the true/false is controlled with the variable wave_happening in my script), Update() doesn't run anymore, so I can't set it to true again.
When I do GameObject.Find("Start Button").SetActive(true) in another script, it just gives me a NullReferenceException (Object reference not set to an instance of an object).
This is my Update() function:
void Update() {
wave_happening = enemy_spawner_script.wave_happening;
Debug.Log(wave_happening);
transform.gameObject.SetActive(wave_happening);
}
Is there a solution to stop this problem, or another way to hide a button?
I'm fairly new to Unity and C#, so I don't know very much.
You can try disabling the button's rendering and functionality components:
GetComponent<Button>().enabled = false; // remove functionality
GetComponent<Image>().enabled = false; // remove rendering
Now, adding that to your Update function, plus a few changes for performance so you are not enabling/disabling every single frame, only when needed:
private bool isShowing = true; // or whatever your default value is
void Update() {
wave_happening = enemy_spawner_script.wave_happening;
Debug.Log(wave_happening);
if(wave_happening != isShowing) {
show(wave_happening);
isShowing = wave_happening;
}
}
void show(bool isShow) {
GetComponent<Button>().enabled = isShow; // remove functionality
GetComponent<Image>().enabled = isShow; // remove rendering
}
It's really difficult to do this well. Lots of issues arise:
Inevitably the button will be in a H or V layout group, and, as a basic software engineering issue of course you want it to work whether or not it is in a layout group, as that layout detail may be changed by your designers as the project goes on. For this reason it is a really good idea, as the OP initially guessed, to simply use .SetActive
But then you have the problem of the button being off so Update is not running. A simple solution is just to put the button in a wrapper. That is to say, simply in a UI Panel. Have the button manager script on that wrapper rather than on the button per se.
Then you just have a pretty Property on the manager script,
public bool Showme
{
etc...
and then you can just go ...
void Update()
{
Showme = enemy_spawner_script.wave_happening;
}
Can't get simpler looking code.
However, if you use a wrapper Panel. It is true that you have to be pretty expert at using the UI (particularly the auto sizing stuff) to make it work just the way you want. But, that's part of building Unity expertise unfortunately. :/
The concerns about performance is ... truly ridiculous. There's no difference between Unity's raw code "checking a boolean" and yourself "checking a boolean". However for sure as a matter of style it's crap to poll it in Update. (Note that in point "B" just above, if you're going to have heinously ugly "anti-polling" code, bury it in that Property.)
And then ...
"Trick" solution
A tip in Unity UI is you can add a CanvasGroup anywhere. Why would you do this? It allows you to FADE the whole thing, which, is often a quick solution to achieve what you want.
Hence ..
public CanvasGroup fader;
and then ..
void Update()
{
fader.alpha = enemy_spawner_script.wave_happening ? 1f : 0f;
}
and you're done!

Does Unity validate if updates are neccessary on UI-elements?

I update UI-Components (espcially Text) regularly in my game. My code looks like this:
private void ShowScore(string newScore) {
var scoreText=Find("Score").GetComponent<Text>();
if (scoreText.text!=newScore) scoreText.text=newScore;
}
My idea behind this is to only update the textbox when the value has really changed and so preventing unneccessary updates that might cause performance issues and/or bad UI experience like "flickering" and so on.
Now my question is: Is that even necessary or does Unity itself already the same validation internally?
Since the source code of Unity's UI system is open-source, you can actually look this up. Regarding your question, the specific code is here: https://bitbucket.org/Unity-Technologies/ui/src/31cbc456efd5ed74cba398ec1a101a31f66716db/UnityEngine.UI/UI/Core/Text.cs#lines-212
The relevant part of the setter is
else if (m_Text != value)
{
m_Text = value;
SetVerticesDirty();
SetLayoutDirty();
}
which, as you suspected, makes sure that the element will only be marked as "dirty" (requiring a visual update) if the new text is different from the one it had before

The parameter (a Grid) in my call of a static method gives an object reference required error

There's a better explanation in edit!
I'm learning my way through C# (I'm a student) and I'm making a game on my own. It's MasterMind. I've done all the code, but there is just 1 error that is responsible for not being possible to test my code.
So the function where the call is used is static, aswell as the method itself.
I'll give a code example because I don't know so good how to formulate the problem.
public static void ProcesColors(Models.Rij_Master HuidigConfig, int lengte)
{
/* first get the control, so we can add changes to it */
ucRij try = (ucRij) FindChildInGrid(Res, 0, 0);
switch (lengte)
{
...;
}
}
He gives an error with Res, Res is a grid in a grid on my xaml page.
I've tried the ref keyword, (on the method parameter and in the definition of the parameters in the method), out, static, but none of these work.
The error is:
An object reference is required for the non-static field, method, or property)
So I know it needs a reference because it is a reference type, but I just don't know how to force it.
(Btw, my grid Res is inside a custom made control)
The method where I find my child in my grid is:
private static FrameworkElement FindChildInGrid(Grid g, int row, int col)
{
var childs = g.Children.Cast<FrameworkElement>();
return childs.Where(x => Grid.GetRow(x) == row && Grid.GetColumn(x) == col).FirstOrDefault();
}
I've done some research and there was a lot to find about static methods, variabels, ect... but nono on how to actually call them.
I hope my question is clear, and that's just not something stupid.
Sorry for the long post and already thank you for just reading it,
Greetings!
(deleted some things)
EDIT::
It's better to first read in the bottom where I shall ask 1 defining question, if that one is answered then I won't need all the rest
So... Where to begin with..
I have a class, MasterMind, and a class MasterMind_Row. I've also built some custom controls (that are all nested in one another).
The main idea is, if a user dubbel taps on a color element in the legend (ucLegend control), than it has to change the first available color in the tryout row. So I have 2 things to deal with, in my class MasterMind I have to change the CurrentConfig MasterMind_Row to its values so the class can check for correct colors, and I have to change the colors themselves in the custom control.
The code that you saw for ProcesColors is in my control ucGame.
There it selects the Row control that is on the correct place in the grid, and change it's background from the 4 borders.
I've achieved this by making 4 public properties in the RowClass where in the setter it changes the background of the borders.
This was all a bit of guide throughout my code, so that you would understand what I am trying to achieve.
Now I will give you the code path how it is achieved:
(In ucLegend, where the double click thing is achieved):
private void ProcesColor(object sender, DoubleTappedRoutedEventArgs e)
{
Border brd = (Border) sender;
Brush selectColor = brd.Background;
MasterMind.FillColorsByClick(selectColor);
}
Then we go to the MasterMind class, where the click is processed.
Is this necesearry to be static? or can that be left out (it would solve everything)
public **static** void FillColorsByClick(Brush selectColor)
{
if (arrSelectionChoice.Length < 4)
{
/* resize array */
ExpandArray1Pos(arrSelectionChoice);
/* fill in the array */
arrSelectonChoice[arrSelectonChoice.Length - 1] = selectColor;
/* fill the color in on the right property (color1,color2,color3,color4)
in the MasterMind_Row CurrentConfig */
FillElementsInRowObject();
/* send the currentconfig Row (with it's colors) to the ucLegend control*/
ucSpel.ProcesColors(HuidigConfig, arrSelectieKeuze.Length);
}
}
And now comes the code that I've posted before.
In the control ucGame we have the following:
public **static** void ProcesColors(Models.Rij_Master HuidigConfig, int lengte)
{
/* first select rowcontrol to make changes to it */
ucRij tryout = (ucRij) FindChildInGrid(ref Res, 0, 0);
switch (lengte)
{
case 1:
tryout.Color1SetBackground = HuidigConfig.Color1;
case 2:
tryout.Color1SetBackground = HuidigConfig.Color1;
tryout.Color2SetBackground = HuidigConfig.Color2;
case 3:
tryout.Color1SetBackground = HuidigConfig.Color1;
tryout.Color2SetBackground = HuidigConfig.Color2;
tryout.Color3SetBackground = HuidigConfig.Color3;
case 4:
tryout.Color1SetBackground = HuidigConfig.Color1;
tryout.Color2SetBackground = HuidigConfig.Color2;
tryout.Color3SetBackground = HuidigConfig.Color3;
tryout.Color4SetBackground = HuidigConfig.Color4;
}
}
What the main question is now, while taking a look again too my code is, is it neceseary that they all have the static property? I know once you made 1 static, you have to make the whole chain as static, but is it possible to do this without a static?
The grid that I am taking out to select the element, is also in my ucGame control. So I can't pass it from the class or anywhere else.
I know this is a really long post, and I am really thankfull for the people who are reading this. I'm still a learning programmer but I wan't to become better, professional, therefore this project that I've made up for myself.
I have found already so many answers on stackoverflow, and am starting to become an active member myself too. I really like this site.
Sorry again for the long post, I hope this is not a stupid question..
My sincerely
I'm assuming that ProcesColors is a function you've added to your window's code-behind.
The problem, as you've alluded to, is that your ProcesColors function is static whereas Res is an instance of a Grid that's on your page. Therefore, ProcesColors doesn't know what Res is. If ProcesColors is really supposed to stay static, then you would need to pass Res into it from whatever code calls ProcesColors. Alternatively, perhaps ProcesColors is not really supposed to be static. If you post the code for the function that calls ProcesColors, we might be able to help you out further.

ArgumentException when trying to add a child to Canvas

I have some mysterious ArgumentException I have been beating the whole day - still have no idea why does it happen.
I have the next simple method in my MainPage:
public void FavsRefresh()
{
favsCanvas.Children.Clear();
for (short i = 0; i < (App.Current as App).favUnits.Count; i++)
{
FavsItems tmpUnit;
(App.Current as App).favUnits.TryGetValue((App.Current as App).ids[i], out tmpUnit);
Canvas.SetTop(tmpUnit.subCanvas, i * 120);
favsCanvas.Children.Add(tmpUnit.subCanvas);
}
}
Here tmpUnit is an instance of my class FavsCanvas. Its code doesn't matter - it merges some elements into Canvas, which is called here subCanvas and a series of them must be added into parent Canvas, called favsCanvas.
The sense in all this, that we have several items initially and the user may delete existing and add new. Every time an item is deleted or added I call this procedure (including initially program loading).
The joke is that it works during loading and when I call it from another pages, but when I call it from class method throws an exception, besides it adds the first element properly and refuses to do that with others.
Every item has unique name, I even tried not to use names at all or use random ones - not a chance. I have no idea why this exception appears?!
I call this method using following:
MainPage.MPInstance.FavsRefresh();
This way works good from another pages, but from class - fails. I even left only one line (simple reload those items in Canvas):
private void FavMenuItem_Click(object sender, RoutedEventArgs e)
{
//Delete favorite
if (((MenuItem)sender).Header.ToString() == AppRes.FavsMenuDeleteFav)
{
MainPage.MPInstance.FavsRefresh();
}
}
The fun is that this code worked when I wrote it first a couple weeks ago, but now somehow stopped.
Another thing I tried is to make this particular call from a method in App.xaml.cs, which in its turn is called from the class, but it didn't help either.
In fact I have studied most of parameters - everything is the same in both cases: when it works and when not, except the place from where the method is called. But I don't see any proper alternative.
--- added 05 Aug.
I am not sure if it is important, but it always point the next line after the line where exception is thrown:
(the forum does not allow me to post images, so here.
I tried to move this method to class both to class itself and to App.xaml.cs - the same problem.
It works properly when is called during loading (my MainPage is Pivot and this page which contains this favCanvas is one of the pivots, but not the first) and when I call it from another page while overriding its OnNavigatingFrom. And when it is called while the the MainPage and this pivot is active. May be something with that?
Well guys, I still can't catch the reason itself, but at last have found a dirty way to walk around.
The page, for some reason, does not like to be modified when it is active (at least this way). So for now I am forced to simply redirect to another page, where I just call my method and then go back.
If you have some ideas, they are still of demand as my current was is like a crutch and I dislike it

how can retrieve a checked list of objects from ObjectListView c#?

I'm using ObjectListView with checkboxes, I would like to run a function on selected items to delete them. So i tried this Method but it not working:
private List<Matricule> matrs;
private void button1_Click(object sender, EventArgs e)
{
//List<Song> s = this.olvSongs.CheckedObjects.Count;
//MessageBox.Show(this.olvSongs.CheckedItems.Count + " " + this.olvSongs.CheckedObjects.Count);
string s = "";
foreach (var item in olvMatrs.SelectedItems)
{
matrs.Remove((Matricule)item);
}
this.olvSongs.SetObjects(matrs);
}
how can i do this task.
You talk about check boxes. The line
foreach (var item in olvMatrs.SelectedItems)
iterates through the ITEMS that are SELECTED, not CHECKED! Is that really what you want?
To get the CHECKED OBJECTS use
objectListView1.CheckedObjects
If you really want to get the SELECTED OBJECTS, don't use Selected*Items*. Use
objectListView1.SelectedObjects;
instead. Thats what the OLV is all about. You want to work with the objects, not with ListViewItems.
If you decided WHAT you want to remove, don't remove the objects from your List, but directly from your ObjectListView using
objectListView1.RemoveObjects(myObjects);
You should probably (re-)read this. Especially the section "Mental gear shift - This is important. You need to understand this.".
well, i see that you don't show the code where it adds anything to matrs, so we are certainly short of useful source code. Also, we don't know what a Matricule is, but i can take a pretty good guess with what you already shared.
i believe 1 of 3 things must be happening if matrs is not getting any items removed.
1: are you sure your function is tied to the click event of the button? you can set a break point in the function to make sure it is even executing. or you can add a line to show a messagebox MessageBox.Show("Yes", this.Text); inside that button1_Click() method.
2: if the function is being executed (so it is not option #1), then my 2nd consideration is that perhaps the (Matricule)item is not in the matrs List to be able to be deleted. that Remove function returns a boolean value indicating whether the remove actually deleted something or not.
3: are you sure it is not getting deleted and that what is really happening is that it really is being deleted but your new updated List is not being shown to you?
I believe you think it is #2, but might want to eliminated the possibility of the other 2 easier options (#1 and #3) first. if you do deduce it to be #2, so options #1 and #3 are not happening, then here's the thing with deleting objects by referencing those objects: it easily leads to problems like what you are having. it is so easy to have code that actually attempts to delete a new object with the same properties as another object that is in a List. the clean way that i solve this is try to remove items by their index # rather than a reference to the object itself. but you are not even grabbing the object to be deleted from the List itself. you are grabbing that object from olvMatrs, which is another object list. my best guess from the information you shared is that this is why it's not working, that if you look deeper, that you are trying to Remove an object that is not in the list, so nothing is being Removed. it's an easy mistake to make. i only know because i've done it too before i learned to be super careful about this.

Categories