Unity 2D Setting the value of a Scrollbar to 0 - c#

I just wanted to set the value of the scrollbar of my scrollview object to 0 every time a new text object is created. This way the newest object would always remain at the bottom without needing to scroll down (think of the chat of a video game for example). My problem is that for some reason the program is setting the value to 0 and then creating the text object, so it keeps the 2nd newest object at the bottom of the scrollview.
My code:
//Creates the Text objects
private GameObject GenerateTextObject(string content)
{
//Creates the Text objects
GameObject messagePrefab = Instantiate(MessagePrefab);
Text textComponent = messagePrefab.GetComponent<Text>();
//Sets the content of the text and parent
textComponent.text = content;
messagePrefab.transform.SetParent(ContentPanel.transform, false);
ScrollbarVertical.value = 0;
return messagePrefab;
}
In the code I am setting the value at the end of the function, but still sets the value of the scrollbar to 0 (properly moving the scrollview to the bottom) before the object is created.
https://gyazo.com/897982521f13d7792ec26540490a40c0
In the Gyazo picture you can see how it doesn't scroll all the way down.
I have tried using a coroutine and waitForEndFrame aswell as waitforseconds(1), but none seem to work.
Edit: when loading up Unity and sending new messages to the scrollview, I see the scrollbar go all the way down and then really quickly move up just a bit hiding the new text object.

What you can do is create a new Scroll View and follow these steps:
The result is from calling GenerateTextObject(Time.time.ToString()); every second.
private GameObject GenerateTextObject(string content)
{
//Creates the Text objects
GameObject messagePrefab = Instantiate(MessagePrefab);
Text textComponent = messagePrefab.GetComponent<Text>();
//Sets the content of the text and parent
textComponent.text = content;
messagePrefab.transform.SetParent(ContentPanel.transform, false);
//Force reset to the bottom on each message
//ScrollbarVertical.value = 0;
return messagePrefab;
}

I have not come up with a proper fix for this problem (I honestly do not know the cause of problem), but I have put together a little work-around for this situation.
To do this I simply created a small function:
public void Testing()
{
if (testing == true)
{
ScrollbarVertical.value = 0;
testing = false;
}
}
This would be attached to the built-in functionality of scrollbars "On Value Changed (Single)". In order to allow this to work, I just change the bool "testing" to true after creating a text object on GenerateTextObject(string content).
This fix is really ugly, and probably would cause more problems in the near future, but for now, it is a quick and dirty solution for this problem.

Sorry to necro this, but I had a similar problem and googling couldn't find a good answer. So I'm posting the solution that I found.
I tried to change the scrollbar value to 1 or 0, depending on the direction, so that everytime I open a UI panel, it'll start at the top. But no matter what I did, nothing seemed to work.
So my solution is to change the Y position of the content instead of the scrollbar value.
My scrollbar direction is set at bottom to top. Then I take the sizeDelta of the content, divide it by 2, (since I need the value of 1 for the scrollbar.value). I then multiplied it by -1 (if I dont, it'll start at the bottom). Then I set that value as the Y position of the content.
So far it works like a charm everytime I open my UI panel.
Hope this helps anyone looking for a solution.

Related

Unity 3D: Vertical Layout Group not placing elements where they should be

I am having a problem with my layout groups and keeping my UI consistent. I have a menu that holds two empty elements Blue and Green boxes bellow that get filled at playtime depending on what the player does. The orange boxes are buttons that may add or remove both purple boxes or orange boxes depending on the button pressed. The Gray, Blue, and Green boxes all have Vertical Layout Group and Content Size fitter (vertical fit set to "Prefered Size"). So from my understanding, all children within these boxes should be stacked vertically and will expand and contract the box as needed.
The problem I am having is that the first time I press a button that adds a purple box and expands the blue box, the green box stays where it is. The second time I press a button the Green box shifts where it should be no matter what gets added to the blue box. Conversely if pressing a button for the first time removes purple boxes and contracts the blue box, the green box stays where it is until I press a button again then shifts where it should be.
If I manually change any of the Gray, Blue, or Green boxes attributes such as anchor pivot or scale the boxes rearrange themselves the way it should be.
Am I doing something wrong with Vertical Layout / Content Size fitter? I hope this is enough to go on. I can provide code if need be. I just don't know exactly what to share.
When the button is pressed call this method.
void AcceptUnlockButtonInput(Exit roomExit) {
if (PlayerHasKeyForExit(roomExit)) {
LogAction(roomExit.exitUnlockDescription);
roomExit.attemptedToExit = false;
roomExit.attemptedToUnlock = false;
} else if (roomExit.attemptedToUnlock == false) {
LogAction(roomExit.exitLockedDescription);
}
DisplayActionText();
foreach(Transform child in inputContainer.transform) {
Destroy(child.gameObject);
}
DisplayInputButtons();
Canvas.ForceUpdateCanvases();
}
LogActrion() just simply adds a string to a list then DisplayActionTest() reads off that list and creats/adds gameObjects to the container.
public void DisplayActionText() {
actionLog.ForEach(value => {
var textObject = Instantiate(displayTextPrefab) as GameObject;
var textMeshPro = textObject.GetComponentInChildren<TextMeshProUGUI>();
textMeshPro.text = value;
textObject.transform.SetParent(outputContainer.transform, false);
});
actionLog.Clear();
Canvas.ForceUpdateCanvases();
}
Simply creats a button for each exit and adds the original method to the buttons listener.
public void DisplayInputButtons() {
foreach (var exit in Exits) {
var unlockButton = Instantiate(userActionButton) as GameObject;
newButton.text = buttonText;
newButton.transform.SetParent(buttonGroup.transform, false);
unlockButton.onClick.AddListener(() => AcceptUnlockButtonInput(exit));
}
}
I have had the same problem with layout groups. From my understanding, layout groups don't update immediately when you put something in them.
Something what worked for me was:
LayoutRebuilder.ForceRebuildLayoutImmediate(recttransform of the layoutgroup);
You can also try disabling and enabling the layout group component:
IEnumerator UpdateLayoutGroup()
{
layoutGroupComponent.enabled = false;
yield return new WaitForEndOfFrame();
layoutGroupComponent.enabled = true;
}
If enabling and disabling the layoutgroup component doesn't work you can also try this:
IEnumerator UpdateLayoutGroup()
{
yield return new WaitForEndOfFrame();
layoutGroupComponent.enabled = false;
layoutGroupComponent.CalculateLayoutInputVertical();
LayoutRebuilder.ForceRebuildLayoutImmediate(recttransform of the layoutgroup);
layoutGroupComponent.enabled = true;
}
I have had similar problems and I found this to be helpful:
https://docs.unity3d.com/ScriptReference/Canvas.ForceUpdateCanvases.html
Sometimes when instantiating elements into a gameobject with a vertical layout group, the elements will only get into their right position after updating the canvas.
Update answer
In my case there were multiple layout groups nested into each other, so the rebuild of the parent layout group did not work. In the first try I rebuild all in hierarchy (old answer), but ran into performance issues for large lists. Then I found a much more robust and performant way (with help from Answer from Adam-Buckner and Auto-Layout Docs):
Only the topmost layout group of the layout group hierarchy has a content size fitter, any descendant must not have one
All vertical layout groups have checked Control Child Size (Width & Height), Use Child Scale (Only Height), Child Force Expand (Only Width)
When the layout is being affected by code, due to adding/removing elements or setting a component active, I use the following code (it uses MarkLayoutForRebuild, which prevents recomputing a node multiple times)
(starting from element with changes, first line can be omitted if element is not a LayoutGroupon its own)
LayoutRebuilder.MarkLayoutForRebuild((RectTransform)transform);
LayoutGroup[] parentLayoutGroups = gameObject.GetComponentsInParent<LayoutGroup>();
foreach (LayoutGroup group in parentLayoutGroups) {
LayoutRebuilder.MarkLayoutForRebuild((RectTransform)group.transform);
}
It works really well and I have no longer elements jumping around. As far as I understand content size fitters control a RectTransform, but competes with the parent LayoutGroup which also want to control it (therefore the warning in the editor). If only LayoutGroups are nested into each other, the algorithm can easily compute the sizes from bottom up, so the bottom most layout groups derive their size from their children (Caused by Use Child Scale), which is then applied to it by their parent layout group (caused by Control Child Size). LayoutGroups does not affect the RectTransform of the element it is placed on, only on its children. Finally the top element gets a height from the hierarchy which is then applied to itself the content size fitter.
Old and inefficient answer
In my case there were multiple layout groups nested into each other, so the rebuild of the parent layout group did not work. I had to rebuild all in hierarchy, to properly update the layout.
LayoutGroup[] parentLayoutGroups = gameObject.GetComponentsInParent<LayoutGroup>();
foreach (LayoutGroup group in parentLayoutGroups) {
LayoutRebuilder.ForceRebuildLayoutImmediate((RectTransform)group.transform);
}
I know there is an issue with the VLG not updating correctly. Changing any of its value after adding/ removing elements forces its layout to resize and may fix your issue (at least that's the way I do it).
Can you try :
- Add/remove your element
- VerticalLayoutLement.spacing += Random.Range(-1,1);
To be sure it rescales properly i usually do it in a coroutine with an"yield return new WaitForNewFrame()" before the spacing part.
After trying a lot of tricks that I found on the web I felt a little frustrated because none of them worked for me. When I ran the scene all the elements in the vertical layout looked messed, but when I clicked on Pause and Continue they magically fitted perfectly in the right position, so I wrote this simple funcion to emulate it, that works fine in my app:
IEnumerator RefreshPrefab(GameObject prefabInstance)
{
prefabInstance.SetActive(false);
yield return new WaitForSeconds(0.1f);
prefabInstance.SetActive(true);
}
And call it sending as parameter the parent prefab that contains the layout group:
StartCoroutine(RefreshPrefab(instantiatedPrefab));

Why is TextMeshPro not Updating textBounds After Setting text?

I have implemented a tooltip system on hover. For this I use a textmeshpro text and an image. This image should be a simple background depending on the length of the text, which is always different.
If I hover over a certain element, the textmeshpro text is replaced. The background image is moved to the element and the size is shall change based on the length of the text:
// Hover over slot in
public void OnPointerEnter(PointerEventData eventData) {
if (currentItem != null) {
Debug.Log("hover over slot item in");
GamePlay.EnableCanvas(GamePlay.GetHoverCanvas());
TextMeshProUGUI txtMeshPro = GamePlay.GetHoverCanvas().GetComponentInChildren<TextMeshProUGUI>();
Debug.Log(currentItem.GetItemDescription(currentItem));
txtMeshPro.text = currentItem.GetItemDescription(currentItem);
var hoverCanvasText = txtMeshPro.GetComponent<RectTransform>();
var hoverCanvasTextBG = GamePlay.GetHoverCanvas().GetComponentInChildren<Image>().GetComponent<RectTransform>();
hoverCanvasText.position = new Vector3(this.GetComponent<RectTransform>().position.x, this.GetComponent<RectTransform>().position.y + 50, 0);
hoverCanvasTextBG.position = new Vector3(this.GetComponent<RectTransform>().position.x, this.GetComponent<RectTransform>().position.y + 50, 0);
hoverCanvasTextBG.sizeDelta = new Vector2(txtMeshPro.textBounds.size.x + 15, txtMeshPro.textBounds.size.y + 15);
// No clue why image is not resized properly on first hover
}
}
The weird issue I have is that the first time I hover over the element, the image size wont change.
Replacing the text and changing the position always works without issues.
Issue description:
First hover over element A: Image background size wont change at all.
Second hover over element B: Image background size changes, but has the correct size of element A
Third hover over element A: Image background size changes, but has the correct size of element B
How is this possible? I dont get whats happening here. The code works without any issues for the position, why the same logic does not work for the size.
Note: I am very new to unity, tinkering arround for a few days now, so I could be a very simple issue, but after researching for a few hours now, I haven't found anything useful.
After setting the text of a TextMeshProUGUI, the textBounds and its size are not updated until the following frame. This is caused by Unity's deferred layout rebuilding. In their UI Layout Documentation, it reads:
The rebuild will not happen immediately, but at the end of the current
frame, just before rendering happens. The reason it is not immediate
is that this would cause layouts to be potentially rebuild many times
during the same frame, which would be bad for performance.
I'm gonna go ahead and guess GamePlay.GetHoverCanvas() is maintaining an object that's never destroyed and, therefore, your code is working in all cases except its very first run.
There are probably a few ways you can go about fixing this but the easiest would be to call Canvas.ForceUpdateCanvases() between setting the text and reading the size of the textbounds. It's not the fastest function but it should do the trick.
Another option would be to, instead of a synchronous function, kick off a coroutine that sets the text one one frame, yields the next frame, then reads the textbounds on its second frame of execution. (This would likely lead to the size popping in and might look undesirable).
Instead of refreshing all your Canvases, you can use TMP_Text.ForceMeshUpdate(); right after you assign your string. This makes sure your text-information are correct within the same frame.
If the parents of your text uses Layout-Groups, there is a fair chance that you need to update those as well. You do that with LayoutRebuilder.ForceRebuildLayoutImmediate( [Layout-Group.RectTransform] ); (read more here)
Please note that the order is important.
Assign your text
Rebuild Layout-Group
Force Mesh Update
To prevent the need to force update the Canvas, you can use TextMeshProUGUI.GetPreferredValues() to ascertain ahead of time what the width and height would be for the text currently assigned to it. It'll return a Vector2. You can then use that Vector2 instead of TextMeshProUGUI.textBounds.
Or if you need the size for a certain text without actually assigning it, use TextMeshProUGUI.GetPreferredValues(string text).
There are other overloads of that method to get the size given constraints in either width or height.

Custom Listbox design

I have a list of dates that I want to present. Initially I thought of using datepicker but it doesn't have max days or max months (Unbelievable) and this is a requirement for me.
Hence I thought of generating my own dates and presenting it to the user.
Below is how I want to display it
I am currently using listbox. I tried combobox, grid view but I am facing the same problem in all of them
Problem
When the date is under the two blue lines I want to set it as selected. The only way I can think of is using code by getting position of the line and then get position of each item in listbox to determine which is falling in the range.
Is there a xaml way to do this?
Any other logic besides determining position? I find this logic extremely crude. A good one would be having a collision box or something which is represented by those lines and when an item hits it, it gets selected
Thanks in advance
So after mucking around for like 3 days I found following solution to be useful
First find the midpoint that is located between the two line. (Find horizontal midpoint, then find vertical midpoint). I found the midpoint using the following code
private void DetermineSelectionLineBoundaries()
{
if (SelectionBoundaryUpper == null || HorizontalScrollPanel == null)
{
return;
}
var transform = SelectionBoundaryUpper.TransformToVisual(Window.Current.Content);
_upperStart = transform.TransformPoint(new Point(0, 0));
_upperEnd = new Point(_upperStart.X + 100, _upperStart.Y);
var lowertransform = SelectionBoundaryLower.TransformToVisual(Window.Current.Content);
_lowerStart = lowertransform.TransformPoint(new Point(0, 0));
var midPointHorizontal = CalculateMidpoint(_upperStart, _upperEnd);
var midPointVertical = CalculateMidpoint(_lowerStart, _upperStart);
_midPoint = new Point(midPointHorizontal.X, midPointVertical.Y);
}
Once you have that, on every scroll call
VisualTreeHelper.FindElementsInHostCoordinates(_midPoint, Window.Current.Content).ToList();
This function will give you all the elements at the point. To be honest I dont just get that element I basically get everything on page that falls vertically on same place as the point. Maybe I am doing something wrong. But the good news is I get only one listbox item. So easy to loop through.
Once that is done, just turn on the IsSelected flag.
Hope this helps someone
I could not find any xaml way to do this.

Silverlight different behaviour when being run from website

I am working on a simple Silverlight application. The point of application is to display data loaded from XML file. Data consists of String "password" and Integer "passwordCount". Each loaded position is displayed as a coloured square and whenever user moves mouse over this square it is resized to "passwordCount/10" pixel width or height and the "password" string is displayed on it. Here is an example:
On Mouseover:
Everything works fine if I run it via VisualStudio Run button. The problem is that when I place the script on website, tiles resize in the wrong direction (they become smaller instead of bigger). The text is not displayed either. I don't have the faintest idea why. Silverlight on website opens in a separate window and it looks like that on mouseover:
(source: screenshu.com)
Here is the function I use to animate tiles:
public void rectangle_MouseEnter(object sender, MouseEventArgs e)
{
sbMouseON = new Storyboard();
DoubleAnimation sizeAnimation = new DoubleAnimation();
sizeAnimation.To = passwordCount/10; //passwordCount is always greater than 1000
sizeAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(300));
Storyboard.SetTarget(sizeAnimation, (Rectangle)sender);
Storyboard.SetTargetProperty(sizeAnimation, new PropertyPath(direction));
sbMouseON.Children.Add(sizeAnimation);
Canvas.SetZIndex(rect, 2); //move rectangle up to make animation visible
DrawTextBlock();
sbMouseON.Begin();
}
Mods: Can't Add Comment so posting as answer
Here I am assuming that the "direction" you are passing in the TargetProperty is "(FrameworkElement.Width)" to increase the width of the rectangle.
If so only Logical Reason a Object can reduce in size is "sizeAnimation.To" property you are setting is setting the value which is smaller than the actual width of the object.
What is this DrawTextBlock() doing ? is there any code in there which can change the behavior of animation ?
The problem solved itself. I've just copied all files onto server and it works great. Looks like it was some Windows problem. Thank you for your interest #Abhinav.

ScatterView item orientation and PrimarySurfaceDevice.Tilt

I'm working in Visual C# 2010 Express with WPF to develop an application for a Microsoft Surface table, and cannot for the life of me find the simple solution to this problem.
When using a ScatterView control without setting a custom template for scatterview items, the control throws new ScatterViewItems onto the screen at random orientations and positions. This, I like.
I am developing my program on a vertical screen, on which scatterViewItems are randomly thrown, except they are mostly upright, and never thrown on updide-down. What I am getting at is the control knows the screen is vertical and keeps the items from starting out upside down.
However, when I transfer the program to a device with a horizontal screen, the items are now thrown out randomly and OFTEN upside down from the original app orientation. Again the ScatterView control is reading InteractiveSurface.PrimarySurfaceDevice.Tilt (or something like it) and compensating.
How do I make it stop doing this?
I just want my items to continue to appear in the ScatterView as if the screen was vertical. Can I trick ScatterView into thinking it's vertical still? Can I turn this feature off? I would like to avoid making my own template for ScatterViewItems.
Thanks!
Set the Orientation property to 0 for each item. The randomness only happens when you haven't explicitly set a value
Well, if nobody can explain how ScatterView knows the screen tilt angle, and how to change that, then the only solution is to generate your own randomness. For reference, here's what I used to do this:
ScatterViewItem item = new ScatterViewItem();
item.Content = image;
Random rand = new Random();
item.Orientation = RandOrientation(-20, 20);
item.Center = RandCenter(50);
SSScatter.Items.Add(item);
private Point RandCenter(int pad)
{
int randx = rand.Next(pad, (int)SSScatter.Width-pad);
int randy = rand.Next(pad, (int)SSScatter.Height-pad);
return new Point(randx, randy);
}
private int RandOrientation(int low, int up)
{
int randor = rand.Next(low, up);
return randor;
}
I still wish there was a way to trick ScatterView...

Categories

Resources