I am using Blazor. I have a grid of buttons in a component and am dynamically setting their individual background-image and border-style attributes with variables returned by methods as users perform certain clicks in other components.
#for (int id = 0; id < 24; id++)
{
var buttonId = id;
<button style="background-image: url(#GetImageFilePath(id)); border-style: #GetBorderStyle(id)"
#onclick="#(e => PlayerGridClick(e, buttonId))"></button>
}
Both methods in the above code return strings and so the code dynamically updates when NotifyStateChanged() action is invoked by the service that provides the data for the component.
If I view the source code in the browser I confirm that both attributes change just as I require, however, only the image attribute is rendered by the browser. If I refresh the browser the border-style attribute is then actioned.
I have tried Chrome and Firefox and both act the same.
Anyone out there have any clues?
Thanks
Further info requested:
The data service implements NotifyStateChanged like this:
public event Action OnChange;
private void NotifyStateChanged() => OnChange?.Invoke();
The client components subscribe like this:
// Subscribe to the state of change notification for the TurnSerice
protected override void OnInitialized()
{
turnService.OnChange += StateHasChanged;
}
// Remove subscription to avoid memory leak
public void Dispose()
{
turnService.OnChange -= StateHasChanged;
}
The client retrieves the data like this:
protected override async Task OnInitializedAsync()
{
player = await turnService.GetPlayerAsync(nPlayer);
// if player has piece in hand:
if(player.pieceInHand.pieceType!=ePieceType.none)
{
// calculate where they can place the piece without it being flipped
// which column?
int column = player.nPlotSource % 6;
// highlight all square in this column
bButtonBorderOn[column] = true;
bButtonBorderOn[column+6] = true;
bButtonBorderOn[column+12] = true;
bButtonBorderOn[column+18] = true;
// which row?
int row = player.nPlotSource / 6;
// highlight all square in this row
bButtonBorderOn[row * 6] = true;
bButtonBorderOn[row * 6+1] = true;
bButtonBorderOn[row * 6+2] = true;
bButtonBorderOn[row * 6+3] = true;
bButtonBorderOn[row * 6+4] = true;
bButtonBorderOn[row * 6+5] = true;
}
else
{
for (int i = 0; i < 24; i++)
bButtonBorderOn[i] = false;
}
//turnService.Test();
}
The setting of the bButtonBorderOn is rather crude but it is just to get it working. Finally the variable is set during the rendering of the component like this:
protected string GetBorderStyle(int id)
{
if (bButtonBorderOn[id])
return "solid";
return "none";
}
Thanks again :)
I have solved it.
The purpose of the dynamic styling was to highlight to a player of the game where they could potentially place a token and get bonus points. So despite the highlighting not working, I moved on to implement the placement rules and found that the data had not initialised properly - extra points were not being awarded.
To solve the placement rule problem I moved the data variable for the styling to the data source class. In doing this the styling problem was also solved.
I am still a beginner to Blazor. Lesson learned: Components are not reliable keepers of data :)
Thanks to all those who took the time to read my question.
Related
I'm currently working with a folder of JSON files which are collected through a tracking experiment with a drone. The data contains position, rotation and timestamp of the drone while it's moving and levitating inside the tracking system.
What I'm currently doing is trying to simulate the movement of the drone inside Unity using those data. So far, I've managed to parse the position and rotation from the data to an object inside Unity and extract the timestamp to System.DateTime in Unity.
However, I don't how to work with the timestamp. I want to use the timestamp to match the position and rotation of the object (i.e: at this timestamp, the drone should be at this position(x,y,z) and has the rotation(x,y,z,w)). Can someone help me with this problem, really appreciate your help :D Here is my current code:
void Update()
{
if (loaded)
{
for(int i = 0; i <= pos_data.Count; i+= 10)
{
Cube.transform.position = pos_data[i];
Cube.transform.rotation = rot_data[i];
}
}
else
{
LoadJson();
//startTime = datetime[0];
loaded = true;
}
}
public void LoadJson()
{
string HeadPath = #Application.dataPath + "/Data/" + "drone_data_1.json";
string HeadJsonhold = File.ReadAllText(HeadPath);
var data_ = JSON.Parse(HeadJsonhold);
for (int rows = 0; rows <= data_.Count; rows += 10)
{
pos_data.Add(new Vector3(data_[rows]["location"]["x"].AsFloat, data_[rows]["location"]["y"].AsFloat, data_[rows]["location"]["z"].AsFloat));
rot_data.Add(new Quaternion(data_[rows]["rotation"]["x"].AsFloat, data_[rows]["rotation"]["y"].AsFloat, data_[rows]["rotation"]["z"].AsFloat, data_[rows]["rotation"]["w"].AsFloat));
Time = System.DateTime.ParseExact(data_[rows]["Timestamp"], "yyyyMMddHHmmss",null);
//Debug.Log(Time);
}
}
If I understand you correctly what you are getting are samples of a real-world drone that at some rate stores keyframes of its movement.
Now you already successfully load that json data but wonder how to animate the Unity object accordingly.
The timestamp itself you can't use at all! ^^
It most probably lies somewhere in the past ;) And you can't just assign something to Time.
What you can do, however, is take the timestamp of the first sample (I will just assume that your samples are all already ordered by the time) and calculate the difference to the next sample and so on.
Then you can use that difference in order to always interpolate between the current and next sample transforms using the given time delta.
Currently you are just doing all samples in one single frame so there won't be any animation at all.
Also just as sidenote:
for(int i = 0; i <= pos_data.Count; i+= 10)
is wrong twice:
a) you already skipped 10 samples when loading the data -> are you sure you now want to again skip 10 of these => In total every time skipping 100 samples?
b) since indices are 0 based the last accessible index would be pos_data.Count - 1 so in general when iterating Lists/arrays it should be i < pos_data.Count ;)
First of all I would suggest you use a better data structure and use one single list holding the information that belongs together instead of multiple parallel lists and rather load your json like e.g.
[Serializable]
public class Sample
{
public readonly Vector3 Position;
public readonly Quaternion Rotation;
public readonly float TimeDelta;
public Sample(Vector3 position, Quaternion rotation, float timeDelta)
{
Position = position;
Rotation = rotation;
TimeDelta = timeDelta;
}
}
And then
// Just making this serialized so you can immediately see in the Inspector
// if your data loaded correctly
[SerializeField] private readonly List<Sample> _samples = new List<Sample>();
public void LoadJson()
{
// start fresh
_samples.Clear();
// See https://learn.microsoft.com/dotnet/api/system.io.path.combine
var path = Path.Combine(Application.dataPath, "Data", "drone_data_1.json");
var json = File.ReadAllText(path);
var data = JSON.Parse(json);
DateTime lastTime = default;
for (var i = 0; i <= data.Count; i += 10)
{
// First I would pre-cache these values
var sample = data[i];
var sampleLocation = sample["location"];
var sampleRotation = sample["rotation"];
var sampleTime = sample["Timestamp"];
// Get your values as you did already
var position = new Vector3(sampleLocation["x"].AsFloat, sampleLocation["y"].AsFloat, sampleLocation["z"].AsFloat));
var rotation = new Quaternion(sampleRotation["x"].AsFloat, sampleRotation["y"].AsFloat, sampleRotation["z"].AsFloat, sampleRotation["w"].AsFloat));
var time = System.DateTime.ParseExact(sampleTime, "yyyyMMddHHmmss", null);
// Now for the first sample there is no deltaTime
// for all others calculate the difference in seconds between the
// last and current sample
// See https://learn.microsoft.com/dotnet/csharp/language-reference/operators/conditional-operator
var deltaTime = i == 0 ? 0f : GetDeltaSeconds(lastTime, time);
// and of course store it for the next iteration
lastTime = time;
// Now you can finally add the sample to the list of samples
// instead of having multiple parallel lists
_samples.Add(new Sample(position, rotation, deltaTime));
}
}
private float GetDeltaSeconds(DateTime first, DateTime second)
{
// See https://learn.microsoft.com/dotnet/api/system.datetime.op_subtraction#System_DateTime_op_Subtraction_System_DateTime_System_DateTime_
var deltaSpan = second - first;
// See https://learn.microsoft.com/dotnet/api/system.timespan.totalseconds#System_TimeSpan_TotalSeconds
return (float)deltaSpan.TotalSeconds;
}
So now what to do with this information?
You now have samples (still assuming ordered by time) holding all required information to be able to interpolate between them.
I would use Coroutines instead of Update, in my eyes they are easier to understand and maintain
// Do your loading **once** in Start
private void Start()
{
LoadJson();
// Then start the animation routine
// I just make it a method so you could also start it later e.g. via button etc
StartAnimation();
}
// A flag just in case to avoid concurrent animations
private bool alreadyAnimating;
// As said just making this a method so you could also remove it from Start
// and call it in any other moment you like
public void StartAnimation()
{
// Only start an animation if there isn't already one running
// See https://docs.unity3d.com/ScriptReference/MonoBehaviour.StartCoroutine.html
if(!alreadyAnimating) StartCoroutine(AnimationRoutine());
}
private IEnumerator AnimationRoutine()
{
// Just in case abort if there is already another animation running
if(alreadyAnimating) yield break;
// Block concurrent routine
alreadyAnimating = true;
// Initially set your object to the first sample
var lastSample = _samples[0];
Cube.transform.position = lastSample.Position;
Cube.transform.rotation = lastSample.Rotation;
// This tells Unity to "pause" the routine here, render this frame
// and continue from here in the next frame
yield return null;
// then iterate through the rest of samples
for(var i = 1; i < _samples.Count; i++)
{
var lastPosition = lastSample.Position;
var lastRottaion = lastSample.Rottaion;
var currentSample = _samples[i];
var targetPosition = sample.Position;
var targetRotation = sample.Rotation;
// How long this interpolation/animation will take
var duration = currentSample.TimeDelta;
// You never know ;)
// See https://docs.unity3d.com/ScriptReference/Mathf.Approximately.html
if(Mathf.Approximately(duration, 0f))
{
Cube.transform.position = targetPosition;
Cube.transform.rotation = targetRotation;
lastSample = currentSample;
continue;
}
// And this is where the animation magic happens
var timePassed = 0f;
while(timePassed < duration)
{
// this factor will be growing linear between 0 and 1
var factor = timePassed / duration;
// Interpolate between the "current" transforms (the ones it had at beginning of this iteration)
// towards the next sample target transforms using the factor between 0 and 1
// See https://docs.unity3d.com/ScriptReference/Vector3.Lerp.html
Cube.transform.position = Vector3.Lerp(lastPosition, targetPosition, factor);
// See https://docs.unity3d.com/ScriptReference/Quaternion.Slerp.html
Cube.transform.rotation = Quaternion.Slerp(lastRotation, targetRotation, factor);
// This tells Unity to "pause" the routine here, render this frame
// and continue from here in the next frame
yield return null;
// increase by the time passed since the last frame was rendered
timePassed += Time.deltaTime;
}
// just to be sure to end with clean values
Cube.transform.position = targetPosition;
Cube.transform.rotation = targetRotation;
lastSample = currentSample;
}
// Allow the next animation to start (or restart this one)
alreadyAnimating = false;
// Additional stuff to do once the animation is done
}
I am trying to lightly customise by inheritance some Elements in Mvvmcross Dialog implementation.
Colors and fonts are getting set fine, but if I try to set the Text Labels frame (to 100 X ... in this example) I cannot get it to stick.
Can anyone point me in the direction of where I am going wrong here? tried it in a couple different places.
public class MyBooleanElement : BooleanElement {
public MyBooleanElement (string caption) : base(caption, false)
{
}
protected override UISwitch CreateSwitch()
{
UISwitch s = base.CreateSwitch();
s.BackgroundColor = UIColor.Clear;
s.Opaque = false;
s.Layer.Opacity = 0.25f;
return s;
}
protected override UITableViewCell GetCellImpl(UITableView tv)
{
var cell = base.GetCellImpl(tv);
if (cell.BackgroundColor != UIColor.Clear) {
cell.BackgroundColor = UIColor.Clear;
cell.TextLabel.Font = Theme.GetContentFont();
cell.TextLabel.TextColor = UIColor.White;
cell.TextLabel.Frame = new RectangleF(100, 0, 320, 20); // this is not holding, resets to 15
cell.ContentView.Frame = new RectangleF(50, cell.ContentView.Frame.Top, cell.ContentView.Frame.Width, cell.ContentView.Frame.Height); // try this also?
}
return cell;
}
protected override void UpdateCaptionDisplay(UITableViewCell cell)
{
base.UpdateCaptionDisplay(cell);
if (cell != null) {
cell.TextLabel.Frame = new RectangleF(100, 0, 320, 20); // this is not holding, resets to 15
cell.TextLabel.BackgroundColor = UIColor.Blue; // Yet this works? some constraints somewhere cannot see in source!
}
}
}
The dialog part of the framework is positioning the headers (labels) and controls for you.
You should look at the source code a bit, maybe try to understand how it works a little bit.
You can start with a breakpoint and step into the source code while debugging. I'm sure you are going to see where the 15 value is being set.
I'm sorry I can't give you an exact answer. I had some issues myself with the incorrect positioning of dialog elements on iPad, and I had to dig a bit.
I have a couple very large datagrids with custom cell renderers. The issue I am facing is that I now have > 80 of these, 1 for each column per data grid.
I'm curious if there is a way I can get these reduced down into 1 global cell renderer that I could pass variables into to define what is allowed by the cell renderer for that column.
ie:
...
col1 = new DataGridColumn("PurchaseStartDate");
col1.headerText = "Purchase Date (YYYY-MM)";
dg.addColumn(col1);
col1.width = 110;
col1.editable = false;
col1.sortable = false;
col1.cellRenderer = Alternating_Row_Colours_editable36;
Alternating_Row_Colours_editable36._dg = dg;
Alternating_Row_Colours_editable36.__enabled = true;
Alternating_Row_Colours_editable36._myCol = 12;
Alternating_Row_Colours_editable36._isNum = 3;
Alternating_Row_Colours_editable36._stage = this;
Alternating_Row_Colours_editable36._maxChars = 9;
within the cell renderer I'm using all of those variables to customize what is allowed.
ie:
...
public function Alternating_Row_Colours_editable36()
{
super();
if(_isNum == 0){
restrict = "a-z A-Z #_,.0-9//-";
maxChars = 64;
}else if (_isNum == 1){
restrict = ".0-9";
maxChars = 9;
}else if (_isNum == 2){
restrict = "0-9";
maxChars = 2;
}else if (_isNum == 3){
restrict = "0-9 \\-";
maxChars = 9;
}else if (_isNum == 4){
restrict = "0-9. %";
maxChars = 9;
}
if(_maxChars != -1){
maxChars = _maxChars;
}
The issue if you look at the above, I just noted that I had an error. I used "//-" to escape the hyphen instead of "\-". Now I've got 80+ changes to make and every time I try to add something new, for a callback, restricts, max chars, making it editable, scrubbing the input, changing it from dynamic to input and back again...
I would love to know if there is a way, to create an instance of the class and use that cell renderer. Or to be able to pass variables that affect only that column and not all columns.
I'm not the best, but I was under the impression that it might just be a set/get function I need to use, or change the variables to protected, private or something to get this desired result.
Anyone have any suggestions on how to bring these 80+ cell renderers under control. As I am not looking forward to needing to make the changes to them for sorting down the road...
jc
I know this is a very late reply and you've more than likely moved on by now!
You can do it by using the information in 'listData' property of the CellRenderer class:
// Create a private class level variable called _dataField...
private var _dataField:String;
// Assign the dataField...
public function set listData(value:ListData):void {
_listData = value;
this._dataField = DataGridColumn(DataGrid(this._listData.owner).columns[this._listData.column]).dataField;
// set the data again now...
this.data = _data;
invalidate(InvalidationType.DATA);
invalidate(InvalidationType.STATE);
}
// Use the dataField when setting value from DataProvider...
public function set data(value:Object):void {
_data = value;
if (this._dataField != "")
{
this.text = value[this._dataField];
}
}
Hope that satisfies any curiosity. Shame they don't just pass that property to the CellRenderer in the first place!
Cheers
I am trying to perform two way binding e.g I have a button (out of many controls), on its selection, I am showing the values of its diff properties(like height, width etc) in some textinput. This one way process works fine.
But the reverse process doesn't work. i.e When I select some button, and try to change its dimension by entering some value in height, width textinputs, the dimension are not changed.
How to know which button was selected by me? How events needs to be handled here ?
private void Form1_Load(object sender, System.EventArgs e)
{
//Create some data and bind it to the grid
dt1 = GetData(1000, 3);
this.UltraGrid1.DataSource = dt1;
//Set the grid's CreationFilter to a new instance of the NumbersInRowSelectors class.
this.UltraGrid1.CreationFilter = new NumbersInRowSelectors();
}
private void UltraGrid1_InitializeLayout(object sender, Infragistics.Win.UltraWinGrid.InitializeLayoutEventArgs e)
{
//Hide the default images that are drawn in the RowSelectors, like the pencil and asterisk, etc.
e.Layout.Override.RowSelectorAppearance.ImageAlpha = Infragistics.Win.Alpha.Transparent;
//Center the text in the RowSelectors.
e.Layout.Override.RowSelectorAppearance.TextHAlign = Infragistics.Win.HAlign.Center;
e.Layout.Override.RowSelectorAppearance.TextVAlign = Infragistics.Win.VAlign.Middle;
//There is no wy to change the width of the RowSelectors.
//Use a smaller font, so that 3-digit numbers will fit.
e.Layout.Override.RowSelectorAppearance.FontData.Name = "Small Fonts";
e.Layout.Override.RowSelectorAppearance.FontData.SizeInPoints = 6;
}
//The NumbersInRowSelectors class. This class Implements a CreationFilter and
//adds a TextUIElement to each RowSelector which displays the row number of
//the row.
public class NumbersInRowSelectors:Infragistics.Win.IUIElementCreationFilter
{
#region Implementation of IUIElementCreationFilter
public void AfterCreateChildElements(Infragistics.Win.UIElement parent)
{
//Don't need to do anything here
}
public bool BeforeCreateChildElements(Infragistics.Win.UIElement parent)
{
//Declare some variables
Infragistics.Win.TextUIElement objTextUIElement;
Infragistics.Win.UltraWinGrid.RowSelectorUIElement objRowSelectorUIElement;
Infragistics.Win.UltraWinGrid.UltraGridRow objRow;
int RowNumber;
//Check to see if the parent is a RowSelectorUIElement. If not,
//we don't need to do anything
if (parent is Infragistics.Win.UltraWinGrid.RowSelectorUIElement)
{
//Get the Row from the RowSelectorsUIElement
objRowSelectorUIElement = (Infragistics.Win.UltraWinGrid.RowSelectorUIElement)parent;
objRow = (Infragistics.Win.UltraWinGrid.UltraGridRow)objRowSelectorUIElement.GetContext(typeof(Infragistics.Win.UltraWinGrid.UltraGridRow));
//Get the Index of the Row, so we can use it as a row number.
RowNumber = objRow.Index;
//Check to see if the TextUIElement is already created. Since
//The RowSelectorsUIElement never has children by default, we
//can just check the count.
if (parent.ChildElements.Count == 0)
{
//Create a new TextUIElement and parent it to the RowSelectorUIElement
objTextUIElement = new Infragistics.Win.TextUIElement(parent, RowNumber.ToString());
parent.ChildElements.Add(objTextUIElement);
}
else
{
//There's already a TextUIElement here, so just set the Text
objTextUIElement = (Infragistics.Win.TextUIElement)parent.ChildElements[0];
objTextUIElement.Text = RowNumber.ToString();
}
//Position the TextUIElement into the RowSelectorUIElement
objTextUIElement.Rect = parent.RectInsideBorders;
//Return True let the grid know we handled this event.
//This doesn't really do anything, since the grid
//does not create any child elements for this object, anyway.
return true;
}
//Return false to let the grid know we did not handle the event.
//This doesn't really do anything, since the grid
//does not create any child elements for this object, anyway.
return false;
}
#endregion
}
}
Create a "currently selected item" member in the class where the button and the text edit are declared.
In the button selection event listener assign the event target to this member. Then use it in the text edit event listener.
For example:
// It's a declaration of the member variable
private var m_current_btn:Button = null;
// It's an event listener for your button
private function on_selection_change(event:Event):void
{
m_current_btn = event.target as Button;
// button_x and button_y are two text edits
button_x.text = m_current_button.x.toString();
button_y.text = m_current_button.y.toString();
}
// Event listener to track changes in the coordinate text inputs
private function on_coordinate_textedit_change(event:Event):void
{
if (m_current_btn != null)
{
m_current_btn.x = parseInt(button_x.text);
m_current_btn.y = parseInt(button_y.text);
}
}
I need to make an auto complete component in flex that fetches the auto complete results from a remote database using a webservice. I have the webservice and querying part worked out. I've already made custom components in action script by extending VBoxes. However I cannot figure out how to generate the popup window that is supposed to show under the text input in my auto complete text box.
Currently I am using something like
PopUpManager.addPopUp(popup, parentComponent);
My popup class extends VBox and it extends the createChildren method as follows
protected override function createChildren():void
{
for (var i:int = 0; i < results.length; i++) {
var itemC:UIComponent =
factory.getComponent(results[i]);
addChild(itemC);
itemC.addEventListener(MouseEvent.CLICK,
getClickFunction(i));
}
private function getClickFunction(index:int):Function {
return function (event:MouseEvent):void
{
selectedIndex = index;
};
}
Unfortunately when the webservice retrieves its results and addPopUp is called, nothing shows up.
Currently the factory.getComponent method is executing this code
public function getComponent(user:Object):UIComponent
{
var email:Label = new Label();
email.text = user.email;
var name:Label = new Label();
name.text = user.displayName;
var vbox:VBox = new VBox();
vbox.addChild(name);
vbox.addChild(email);
return vbox;
}
I think you ought to look for someone who has already implemented this. While your issue is probably related to not positioning and sizing the component before calling addPopup() even if we helped you solve that you still have a lot more work todo. (BTW call super.createChildren in your override or else bad things will happen). Anyway, check this out:
http://www.adobe.com/cfusion/exchange/index.cfm?event=extensionDetail&extid=1047291
Finally I figured out how to use the List control and I stopped using a factory to generate components, instead I use the itemRenderer feature in the list control. I also used that to replace the custom popup class, and I added a positioning function to be called later. By combining these things I was able to get the drop down to display as expected. It appears that some components do not work well as pop ups.
Regardless, the working pop up code is
Inside my autocomplete component which extends HBox
dropDownList = new List();
dropDownList.itemRenderer = itemRenderer;
dropDownList.dataProvider = results;
dropDownList.labelFunction = labelFunction;
dropDownList.rowCount = results.length;
dropDownList.labelFunction = labelFunction==null ?
defaultLabelFunction : labelFunction;
dropDownList.tabFocusEnabled = false;
dropDownList.owner = this;
PopUpManager.addPopUp(IFlexDisplayObject(dropDownList), DisplayObject(this));
callLater(positionDropDownList);
Method in the autocomplete component (textInput is my text field)
public function positionDropDownList():void {
var localPoint:Point = new Point(0, textInput.y);
var globalPoint:Point = localToGlobal(localPoint);
dropDownList.x = globalPoint.x;
var fitsBelow:Boolean = parentApplication.height - globalPoint.y - textInput.height > dropDownList.height;
var fitsAbove:Boolean = globalPoint.y > dropDownList.height;
if (fitsBelow || !fitsAbove) {
dropDownList.y = globalPoint.y + textInput.measuredHeight;
} else {
dropDownList.y = globalPoint.y - dropDownList.height;
}
}
The position function was code that I borrowed from http://hillelcoren.com/flex-autocomplete/