ViewCell ForceUpdateSize not working sometimes - c#

I am using a custom ViewCell in Xamarin Forms which looks like this:
public class NativeCell : ViewCell
{
public double Height
{
get
{
return base.Height;
}
set
{
base.Height = value;
ForceUpdateSize();
}
}
}
I have also created a method to animate the colapse of the ViewCell which works as expected. However, with the overlay that comes with await, each loop iteration lasts up to 100ms instead of 1ms:
async public void Colapse()
{
for (double i = Height; i >= 0; i--)
{
Height = i;
await Task.Delay(1);
}
}
So I made another method that used the Stopwatch to do the waiting:
async public void Colapse()
{
var Timer = new Stopwatch();
for (double i = Height; i >= 0; i--)
{
Height = i;
Timer.Start();
while(Timer.Elapsed.TotalMilliseconds < 1)
{
}
Debug.WriteLine("-->" + Timer.Elapsed.TotalMilliseconds);
Timer.Reset();
}
}
This last method reports that the wait time is now usually 1.1ms - 1.3ms. Which is great. However, the Height asignment is not doing anything. Or at least ForceUpdateSize is not triggering.

We need to solve few problems here.
You have to specify HasUnevenRows in list view
You cannot do "double i = Height" because Height will be "-1"
You last function setting Height using StopWatch is not really async, so you UI will not be updated till function exits.
Because Task.Delay is slow we can use Device timer with tick interval
Below is the solution that I tested on Android. The code is inside ViewCell in PCL
int currentHeight;
public void Colapse()
{
currentHeight = (int)this.View.Height;
Device.StartTimer(new TimeSpan(100), timerTick);
}
private bool timerTick()
{
if (currentHeight <= 0)
return false;
else
{
Height = currentHeight--;
ForceUpdateSize();
return true;
}
}

Related

VirtualizingStackPanel with cache

We have to requirement to virtualize a ListView/ItemsControl with a VirtualizingStackPanel. Although everything works as expected, the Control's ItemTemplate adheres a complex control with a lot of computation during its initialization phase - which has to be done on the UI thread. In other words, scrolling leads to UI freezes - which is fine if it only has to be done once. As we can't use the VirtualizingStackPanel.VirtualizationMode="Recycle" (due to several other restrictions) we have to try something different.
I thought of a "cached" virtualizingStackPanel which doesn't actually dispose the ItemTemplate's Template, but rather 'freezes' the control. When the user scrolls back to a - previously loaded template - we could simply 'unfreeze' the control.
The 'freeze' can be implemented by overwriting OnCleanUpVirtualizedItem, such as:
protected override void OnCleanUpVirtualizedItem(CleanUpVirtualizedItemEventArgs args)
{
var stuff = FindChild<HeavyStuff>(args.UIElement);
if (stuff != null)
{
int idx = Children.IndexOf(args.UIElement);
if (!_buffer.ContainsKey(idx))
_buffer.Add(idx, args.UIElement);
stuff.Freeze();
args.Handled = true;
args.Cancel = true;
}
else
{
base.OnCleanUpVirtualizedItem(args);
}
}
That works pretty well. The control stays within the VisualTree and it simply 'freezes' and avoids any user-input and the potential resulting workload. However, I couldn't figure out on howto 'unfreeze' the control when it comes back into view. I dug through the reference-source and found the BringIndexIntoView, which could potentially solve my issue like the following:
protected override void BringIndexIntoView(int index)
{
if (_buffer.ContainsKey(index))
{
FindChild<HeavyStuff>(_buffer[index]).UnFreeze();
}
else
{
base.BringIndexIntoView(index);
}
}
However, that method never gets called by the internal VirtualizingStackPanel logic. My second thought was to override the IItemContainerGenerator, as the generator does provide the DependencyObjects on demand. But again without any luck. One can't inherit the ItemContainerGenerator, because it is sealed. Secondly, defining a proxy and overwriting the ItemContainerGenerator properties doesn't help either, as the base class doesn't call that VirtualizingStackPanel's ItemContainerGenerator property at all:
public new IItemContainerGenerator ItemContainerGenerator => generator;
Is there any way to obtain the information when a control scrolls back into the view, without the VirtualizingStackPanel re-creating an instance?
Addon: I also thought about virtualizing the data-source itself. However, even if we would virtualize the data source, the global user input would lead the controls to perform CPU and UI-thread intensive operations. Hence, it doesn't matter which way we choose, we do have to 'freeze' and 'unfreeze' certain, non-viewport-related controls. In other words, we need UI virtualization nevertheless.
EDIT: "Freeze" and "Unfreeze" does not refer to the .NET object freezing. My poor choice of words may cause that confusion. With "freeze" and "unfreeze" I do refer to some internal logic which subscribes or unsubscribes from various event handlers, such that controls, beeing out of the viewport, don't require to process that input.
You can use the following example implementation that extends the StackPanel to tracks the visibility of its hosted containers (in terms of the parent scroll viewer's viewport).
Simply set the custom Panel as ItemsPanel to the ListBox.
It's important that the parent ScrollViewer has the CanContentScroll property set to true (which is the default for the ListBox).
Since StackPanel already implements IScrollInfo, observing the scroll event and viewport is very straight forward.
Add your actual implementation, to handle the changed containers and/or their hosted models, to the OnHiddenContainersChanged method to complete the Panel.
public class ScrollWatcherPanel : StackPanel
{
public ScrollWatcherPanel()
{
this.Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
if (!this.ScrollOwner.CanContentScroll)
{
throw new InvalidOperationException("ScrollViewer.CanContentScroll must be enabled.");
}
this.ScrollOwner.ScrollChanged += OnScrollChanged;
}
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
HandleAllContainers();
}
private void OnScrollChanged(object sender, ScrollChangedEventArgs e)
=> HandleContainerVisibilityChanges((int)e.VerticalChange);
private void HandleAllContainers()
{
int containersBeforeViewportStartCount = (int)this.VerticalOffset;
int containersBeforeViewportEndCount = containersBeforeViewportStartCount + (int)this.ViewportHeight + 1;
var newHiddenContainers = new List<FrameworkElement>();
var newVisibleContainers = new List<FrameworkElement>();
for (int childContainerIndex = 0; childContainerIndex < this.InternalChildren.Count; childContainerIndex++)
{
bool isContainerHiddenBeforeViewport = childContainerIndex < containersBeforeViewportStartCount;
bool isContainerVisibleInViewport = childContainerIndex < containersBeforeViewportEndCount;
var childContainer = (FrameworkElement)this.InternalChildren[childContainerIndex];
if (isContainerHiddenBeforeViewport)
{
newHiddenContainers.Add(childContainer);
}
else if (isContainerVisibleInViewport)
{
newVisibleContainers.Add(childContainer);
}
else // Container is hidden after viewport
{
newHiddenContainers.Add(childContainer);
}
}
OnHiddenContainersChanged(newHiddenContainers, newVisibleContainers);
}
private void HandleContainerVisibilityChanges(int verticalChange)
{
int containersBeforeViewportStartCount = (int)this.VerticalOffset;
int containersBeforeViewportEndCount = containersBeforeViewportStartCount + (int)this.ViewportHeight + 1;
int newHiddenContainerCount = Math.Abs(verticalChange);
int newVisibleContainerCount = Math.Abs(verticalChange);
bool isScrollingDown = verticalChange > 0;
int changeCount = Math.Abs(verticalChange);
var newHiddenContainers = new List<FrameworkElement>();
var newVisibleContainers = new List<FrameworkElement>();
int changesIndex = Math.Max(0, containersBeforeViewportStartCount - changeCount);
for (int childContainerIndex = changesIndex; childContainerIndex < this.InternalChildren.Count; childContainerIndex++)
{
bool isContainerHiddenBeforeViewport = childContainerIndex < containersBeforeViewportStartCount;
bool isContainerVisibleInViewport = childContainerIndex < containersBeforeViewportEndCount;
var childContainer = (FrameworkElement)this.InternalChildren[childContainerIndex];
if (isContainerHiddenBeforeViewport)
{
if (isScrollingDown)
{
bool isContainerNewHidden = childContainerIndex >= containersBeforeViewportStartCount - changeCount
&& newHiddenContainerCount > 0;
if (isContainerNewHidden)
{
newHiddenContainers.Add(childContainer);
newHiddenContainerCount--;
}
}
}
else if (isContainerVisibleInViewport)
{
if (isScrollingDown)
{
bool isContainerNewVisible = childContainerIndex >= containersBeforeViewportEndCount - changeCount
&& newVisibleContainerCount > 0;
if (isContainerNewVisible)
{
newVisibleContainers.Add(childContainer);
newVisibleContainerCount--;
}
}
else
{
bool isContainerNewVisible = childContainerIndex >= containersBeforeViewportStartCount
&& newVisibleContainerCount > 0;
if (isContainerNewVisible)
{
newVisibleContainers.Add(childContainer);
newVisibleContainerCount--;
}
}
}
else // Container is hidden after viewport (on scroll up)
{
if (!isScrollingDown)
{
bool isContainerNewHidden = childContainerIndex >= containersBeforeViewportEndCount
&& newHiddenContainerCount > 0;
if (isContainerNewHidden)
{
newHiddenContainers.Add(childContainer);
newHiddenContainerCount--;
if (newHiddenContainerCount == 0)
{
break;
}
}
}
}
}
OnHiddenContainersChanged(newHiddenContainers, newVisibleContainers);
}
protected virtual void OnHiddenContainersChanged(IEnumerable<FrameworkElement> newHiddenContainers, IEnumerable<FrameworkElement> newVisibleContainers)
{
// TODO::Handle "hidden"/"visible" item containers, that are just scrolled out of/into the viewport.
// You can access the DataContext of the containers to get a reference to the underlying data model.
}
}

Real Time chart performance issue c# winform

I am currently using liveChart to plot a real time graph of 3 values: a position, a load and a deformation. The program is based on the Doli.DoPE library (a proprietary dll)
In MainForm.cs, there is an event that is triggered everytime the sensor records a new value (every millisecond or so).
public void Initialisation()
{
//...
MyEdc.Eh.OnDataHdlr += new DoPE.OnDataHdlr(OnData)
//...
}
with
private int OnData(ref DoPE.OnData Data, object Parameter)
{
DoPE.Data Sample = Data.Data;
if (Data.DoPError == DoPE.ERR.NOERROR)
{
Int32 Time = Environment.TickCount;
if ((Time - LastTime) >= 250 /*ms*/)
{
// Send the data from the ondata handler inside of a global list
ListData.time.Add(Sample.Time);
ListData.position.Add(Sample.Sensor[(int)DoPE.SENSOR.SENSOR_S]);
ListData.load.Add(Sample.Sensor[(int)DoPE.SENSOR.SENSOR_F]);
ListData.extend.Add(Sample.Sensor[(int)DoPE.SENSOR.SENSOR_E]);
Thread ThForUpdateChart = new Thread(() =>
{
if (NewINstanceOfChart != null)
{ NewINstanceOfChart.UpdateValues(ListData.time.Last(), ListData.position.Last(),ListData.load.Last(), ListData.extend.Last()); }
});
ThForUpdateChart.Start();
LastTime = Time;
}
}
return 0;
}
The function UpdateValues is part of a second form RealTimeChart.cs called in the MainForm through a button click event:
private void btnGraph_Click(object sender, EventArgs e)
{
var thread = new Thread(() =>
{
NewINstanceOfChart = new RealTimeChart(ListData);
NewINstanceOfChart.Show();
});
thread.Start();
}
the form RealTimeCharts.cs is initalised this way:
public RealTimeChart(Globals ListData)
{
InitializeComponent();
//measures = ListData;
ListPosition = new ChartValues<ObservablePoint>();
for (int i = 0; i < measures.load.Count(); i++)
{
ListPosition.Add(new ObservablePoint
{
X = measures.time[i],
Y = measures.position[i]
});
}
ListLoad = new ChartValues<ObservablePoint>();
for (int i = 0; i < measures.load.Count(); i++)
{
ListLoad.Add(new ObservablePoint
{
X = measures.time[i],
Y = measures.load[i]
});
}
ListExtend = new ChartValues<ObservablePoint>();
for (int i = 0; i < measures.load.Count(); i++)
{
ListExtend.Add(new ObservablePoint
{
X = measures.time[i],
Y = measures.extend[i]
});
}
resultChart.Series.Add(new LineSeries
{
LineSmoothness = 0,
Values = ListPosition,
PointGeometrySize = 2,
StrokeThickness = 4
});
SetXAxisLimits();
}
And the UpdateValues function is defined as followed:
public void UpdateValues(double time, double position, double load, double extend)
{
measures.time.Add(time-measures.TareTime);
measures.position.Add(position);
measures.load.Add(load);
measures.extend.Add(extend);
UpdateEnabledSequencialPartToTrue();
}
public void UpdateEnabledSequencialPartToTrue()
{
if (this.InvokeRequired)
BeginInvoke(new System.Action(() => this.InternalUpdateEnabledSequencialPartToTrue()));
else
InternalUpdateEnabledSequencialPartToTrue();
}
private void InternalUpdateEnabledSequencialPartToTrue()
{
try
{
ListPosition.Add(new ObservablePoint
{
X = measures.time.Last(),
Y = measures.position.Last()
});
ListLoad.Add(new ObservablePoint
{
X = measures.time.Last(),
Y = measures.load.Last()
});
ListExtend.Add(new ObservablePoint
{
X = measures.time.Last(),
Y = measures.extend.Last()
});
//LineSeries plot = new LineSeries();
SetXAxisLimits();
// lets only use the last 14400 values (1h long recording, 14400 values at frequency of 1 record very 250ms, see OnData function MainForm
if (measures.time.Count > 14400)
{
ListPosition.RemoveAt(0);
ListLoad.RemoveAt(0);
ListExtend.RemoveAt(0);
}
}
catch (NullReferenceException) { }
}
After a minute, the programme starts to be really laggy. I tried putting the second winform (RealTimeCharts) on another thread so the MainForm does not lag (it is piloting a machine, it has to be responsive), but no success.
I would like to know if the whole thing is laggy because the code is way too bad, or if it is liveChart that reached its (free) limits. Would you advice another way to plot real time data ?
In MainForm.cs, there is an event that is triggered everytime the sensor records a new value (every millisecond or so).
That is natturally way higher then what Winforms Drawing can take. See, drawing a GUI is expensive. If you only do it once per user-triggered event, you will never notice that. But do it from a loop - including sampling a sensor every MS - and you can quickly overlord the UI. My first Multithreading tests actually appeared to have failed on big numbers, becaus I ended up sending so many updates I plain overloaded the GUI thread. Since then I know not to go past progress bars.
You can add data to a background collection as quickly as you can sample them, but you can not draw that quickly. And honestly drawing more often then 30-60 times/second (every ~17 ms) is not really going to help anyone anyway. Usually you can not use a timer, as the Tick might happen more often then it can be processed - again, a GUI Thread with a overflowing Event Queue.
I do not have any rate limiting code for WindowsForms. But I would guess an Event that re-queues itself at the end of the EventQueue after finishing the work would work.

Game Object invisible for a given time

Good day everyone, currently I am developing a simple 2D game using SWINGAME. I have set a collision between 2 objects. So when they collide, I want to temporarily make one of them invisible for a certain time. I am stuck about the time component, let's say I want the object to be invisible for 3 seconds after that it will change back to the default object. Below are the two images, if the collision is true then it will display image2, or else display image1. BTW I use a different image to indicate the invisibility. Here's my code.
Player class:
public void Draw ()
{
if (invisible == true) {
if(elapsedTime <= 3.0){
elapsedTime += elapsedTime;
SwinGame.DrawBitmap ("image2.png", (float)X, (float)Y);
}
}else {
elapsedTime = 0;
SwinGame.DrawBitmap ("image1.png", (float)X, (float)Y);
}
}
public bool Invisible {
get { return invisible; }
set { invisible = value; }
}
Object Collision class :
{... //Some codes
for(int i = 0; i < _obstacles.Count; i++)
{
if (_obstacles [i] is Invisible) {
p.Invisible = true;
p.Draw ();
}
}
//Some codes ...}
This should help you to calculate the time accurately by using the StopWatch class:
//somewhere in your code
Stopwatch sw = new Stopwatch();
sw.Start();
public void Draw ()
{
if (invisible == true) {
if(sw.ElapsedMilliseconds <= 3000){
SwinGame.DrawBitmap ("image2.png", (float)X, (float)Y);
}
}else {
sw.Restart();
SwinGame.DrawBitmap ("image1.png", (float)X, (float)Y);
}
}

Making my program pause between drawing cards

I have made a little Blackjack game, and I'd like to make the computer wait between each card he pulls, however using System.Threading.Thread.Sleep(int x) does not make the program wait between cards, but makes it wait for x * amount of cards..
I also know that using Thread.Sleep is not a good way, so I'd rather learn a better way as I am creating this program entirely for educative purposes.
I'll add the code underneath which decides whether or not a card should be drawn, and the method that draws the card.
private void ComputerTurn()
{
drawCard.Enabled = false;
finishTurn.Enabled = false;
while (computerTotalScore <= 11)
{
ComputerDrawCard();
}
drawAgain = true;
while (drawAgain)
{
ComputerDrawCard();
if (totalScore <= 21)
{
if (computerTotalScore > totalScore)
{
drawAgain = false;
}
else
{
drawAgain = true;
}
}
else
{
if (computerTotalScore > 16)
{
drawAgain = false;
}
else
{
drawAgain = true;
}
}
}
DecideWinner();
}
public void ComputerDrawCard()
{
cardAlreadyPulled = true;
while (cardAlreadyPulled)
{
cardType = random.Next(0, 4);
cardNumber = random.Next(0, 13);
if (!pulledCards[cardType, cardNumber])
{
cardAlreadyPulled = false;
pulledCards[cardType, cardNumber] = true;
}
}
ComputerUpdateCardPictures();
computerScore = cardScores[cardNumber];
if (computerScore == 1)
{
if (computerTotalScore <= 10)
{
computerScore = 11;
}
else
{
computerScore = 1;
}
}
computerTotalScore += computerScore;
txtComputerCurrentScore.Text = computerScore.ToString();
txtComputerTotalScore.Text = computerTotalScore.ToString();
System.Threading.Thread.Sleep(random.Next(250, 750));
}
There are multiple ways to achieve something like this. I believe what you're attempting to do is simulate a human taking time to do things. I recommend using a combination of expected wait times and a timer to achieve what you want.
class Example
{
public Example()
{
// create a stalled timer
_pulse = new Timer(this.TakeAction);
}
TimeSpan _drawdelay = TimeSpan.FromSeconds(2);
DateTime _lastAction = DateTime.MinValue;
Timer _pulse;
public void Start()
{
// start the timer by asking it to call the worker method ever 0.5 seconds
_pulse.Change(0, 500);
}
public void Stop()
{
// stop the timer by setting the next pulse to infinitely in the future
_pulse.Change(Timeout.Infinite, Timeout.Infinite);
}
void TakeAction(object x)
{
lock (_pulse)
{
DateTime now = DateTime.Now;
if(now - _lastAction > _drawdelay)
{
// do work ...
_lastAction = now;
}
}
}
}
That said, the above code will run into issues if the work being done takes longer than 500 milliseconds to complete. Add thread safety as necessary.
I would add a last time drawn and time between draws members. Then before drawing a card, get the time between now and the last time pulled. If the time is greater than the allowed time between the draws its cool to draw.
private DateTime _lastWrite = DateTime.Now;
private TimeSpan _delay = TimeSpan.FromMilliseconds(100);
public void ComputerDrawCard() {
var now = DateTime.Now;
if (now - _lastWrite < _delay)
return;
_lastWrite = now;
draw card...
}
Here's a gist of an example working correctly.

Adding a timer to the Kinect Click function C#

I have created a click function for Kinect without using any gestures.. its simple and it works.. however i want the function to wait.. my counter isnt seem to be working .. what I want to do is.. IF my hand is on the button for lets say more than 3 seconds.. then return true ..any method to do it? Counter doesnt seem to be working
public bool KinectClick(int x,int y)
{
if ((x >= position.X && x <= position.X +position.Width) && (y >= position.Y && y <= position.Y + position.Height))
{
// time.Start();
int counter = 0;
while (true)
{
counter++;
if (counter >= 8000)
{
return true;
counter = 0;
}
}
}
I use a DispatcherTimer to accomplish the same thing you are trying to do. A simple form could look something like this:
private DispatcherTimer hitTestTimer = new DispatcherTimer();
private int timerCount = 5;
public MyConstructor() {
hitTestTimer.Tick += OnHitTestTimerTick;
hitTestTimer.Interval = new TimeSpan(0, 0, 1);
}
private void OnHitTestTimerTick(object sender, EventArgs e)
{
if (timerCount > 1)
{
timerCount--;
}
else
{
// CLICK!
}
}
You can add flags that toggle when you first enter your object, and check that to verify if you have (or haven't) left the object since the last timer tick.

Categories

Resources