.net DispatcherTimer fires tick event repeatedly - c#

i have problem in my code that i don't even near to understand.
Here is my item interface;
internal interface IItem
{
void Show();
event EventHandler Completed;
TimeSpan Duration { get; set; }
string Name { get; set; }
}
internal class ItemImage : IItem
{
public TimeSpan Duration { get; set; }
public string Name { get; set; }
public event EventHandler Completed;
private DispatcherTimer _dt = new DispatcherTimer();
public void Show()
{
_dt.Interval = this.Duration;
_dt.Tick += (s, e) =>
{
_dt.Stop();
Completed(this, new EventArgs());
};
_dt.Start();
}
}
And here's my player:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
int _pIndex = 0;
List<IItem> list = new List<IItem>();
private void Button1_Click(object sender, RoutedEventArgs e)
{
list = new List<IItem>()
{
new ItemImage() { Duration = TimeSpan.FromSeconds(5), Name = "Image1" },
new ItemImage() { Duration = TimeSpan.FromSeconds(3), Name = "Image2" },
new ItemImage() { Duration = TimeSpan.FromSeconds(5), Name = "Image3" },
new ItemImage() { Duration = TimeSpan.FromSeconds(7), Name = "Image4" }
};
Next();
}
void Next()
{
var tb = new TextBlock();
tb.Text = ((IItem)list[_pIndex]).Name;
StackPanel1.Children.Add(tb);
list[_pIndex].Completed += (s, e) =>
{
Next();
};
list[_pIndex].Show();
_pIndex++;
_pIndex %= list.Count;
}
}
First list plays with no problem but on second turn DispatcherTimer doesn't wait for my duration value, and immediately fires complete event. What do i do wrong?
Thanks.

I don't know exactly what is happening (I didn't test it), but I see that every time you call Show(), another eventhandler is attached to the Tick in your ItemImage object. This could lead to some side effects you'll experiencing.
You might change it to:
internal class ItemImage : IItem
{
public TimeSpan Duration { get; set; }
public string Name { get; set; }
public event EventHandler Completed;
private DispatcherTimer _dt = new DispatcherTimer();
// constructor
public ItemImage()
{
_dt.Tick += (s, e) =>
{
_dt.Stop();
Completed(this, new EventArgs());
};
}
public void Show()
{
_dt.Interval = this.Duration;
_dt.Start();
}
}
You could recreate the DispatcherTimer or move the event attaching to the constructor. (like above)
This is also done in the Next() method with list[_pIndex].Completed. (it attaches to a class member, so every buttonclick new handlers are added to the list.)
You might reconcider the style of attaching events. Like moving them to constructors.
Like:
public partial class MainWindow : Window
{
int _pIndex = 0;
List<IItem> list = new List<IItem>();
public MainWindow()
{
InitializeComponent();
list[_pIndex].Completed += (s, e) =>
{
_pIndex++;
_pIndex %= list.Count;
Next();
};
}
private void Button1_Click(object sender, RoutedEventArgs e)
{
list = new List<IItem>()
{
new ItemImage() { Duration = TimeSpan.FromSeconds(5), Name = "Image1" },
new ItemImage() { Duration = TimeSpan.FromSeconds(3), Name = "Image2" },
new ItemImage() { Duration = TimeSpan.FromSeconds(5), Name = "Image3" },
new ItemImage() { Duration = TimeSpan.FromSeconds(7), Name = "Image4" }
};
Next();
}
void Next()
{
var tb = new TextBlock();
tb.Text = ((IItem)list[_pIndex]).Name;
StackPanel1.Children.Add(tb);
list[_pIndex].Show();
}
}
Good luck.

Related

C# Xamarin.Android Calling Dialogfragment from recyclerView

I would like to display diaglog fragment by clicking a button/imageView (By clicking the sAddButton)from recyclerView. But when I clicked the button nothing shows up.
How do I achieve that here is my code.
My RecyclerView Adapter
I added an pulic eventhandler in myadapter (StockInItemClick)
then created a delegate method to invoke the eventhandler(OnStockInClick)
then pass the delegate method as parameter on my adapterview holder
then Implement the delegate method on my adapterview holder.
internal class StocksAdapter : RecyclerView.Adapter
{
public event EventHandler<StocksAdapterClickEventArgs> ItemClick;
public event EventHandler<StocksAdapterClickEventArgs> ItemLongClick;
public event EventHandler<StocksAdapterClickEventArgs> StockInItemClick;
List<Products> items;
public StocksAdapter(List<Products> data)
{
items = data;
}
// Create new views (invoked by the layout manager)
public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
{
//Setup your layout here
View itemView = LayoutInflater.From(parent.Context).Inflate(Resource.Layout.stock_rView, parent, false);
//var id = Resource.Layout.__YOUR_ITEM_HERE;
//itemView = LayoutInflater.From(parent.Context).
// Inflate(id, parent, false);
var vh = new StocksAdapterViewHolder(itemView, OnClick, OnLongClick, OnStockInClick);
return vh;
}
// Replace the contents of a view (invoked by the layout manager)
public override void OnBindViewHolder(RecyclerView.ViewHolder viewHolder, int position)
{
var holder = viewHolder as StocksAdapterViewHolder;
//holder.TextView.Text = items[position];
holder.sNameText.Text = items[position].PName;
holder.sQtyText.Text = items[position].QtyonHand;
}
public override int ItemCount => items.Count;
void OnClick(StocksAdapterClickEventArgs args) => ItemClick?.Invoke(this, args);
void OnLongClick(StocksAdapterClickEventArgs args) => ItemLongClick?.Invoke(this, args);
void OnStockInClick(StocksAdapterClickEventArgs args) => StockInItemClick?.Invoke(this, args);
}
public class StocksAdapterViewHolder : RecyclerView.ViewHolder
{
//public TextView TextView { get; set; }
public TextView sNameText { get; set; }
public TextView sQtyText { get; set; }
public ImageButton sAddButton { get; set; }
public StocksAdapterViewHolder(View itemView, Action<StocksAdapterClickEventArgs> clickListener,
Action<StocksAdapterClickEventArgs> longClickListener,
Action<StocksAdapterClickEventArgs> stockInClickListener) : base(itemView)
{
//TextView = v;
sNameText = (TextView)itemView.FindViewById(Resource.Id.sNameTView);
sQtyText = (TextView)itemView.FindViewById(Resource.Id.qtyonhandTView);
sAddButton = (ImageButton)itemView.FindViewById(Resource.Id.addButton);
itemView.Click += (sender, e) => clickListener(new StocksAdapterClickEventArgs { View = itemView, Position = AdapterPosition });
itemView.LongClick += (sender, e) => longClickListener(new StocksAdapterClickEventArgs { View = itemView, Position = AdapterPosition });
sAddButton.Click += (sender, e) => longClickListener(new StocksAdapterClickEventArgs { View = itemView, Position = AdapterPosition });
}
}
public class StocksAdapterClickEventArgs : EventArgs
{
public View View { get; set; }
public int Position { get; set; }
}
my code in activity
I implement the eventhandler
then on the event onlcick event(Adapter_StockInItemClick) I called the
dialog fragment.
private void SetupRecyclerView()
{
stockRView.SetLayoutManager(new Android.Support.V7.Widget.LinearLayoutManager(stockRView.Context));
adapter = new StocksAdapter(StockList);
adapter.StockInItemClick += Adapter_StockInItemClick;
stockRView.SetAdapter(adapter);
}
private void Adapter_StockInItemClick(object sender, StocksAdapterClickEventArgs e)
{
Products thisproducts = StockList[e.Position];
addNewStockInProductsFragment = new AddNewStockInProductsFragment(thisproducts);
var trans = SupportFragmentManager.BeginTransaction();
addNewStockInProductsFragment.Show(trans, "StockIn");
}
Found the problem
sAddButton.Click += (sender, e) => longClickListener(new StocksAdapterClickEventArgs { View = itemView, Position = AdapterPosition });
shouldbe (deleteClickListener) not (longClickListener)
sAddButton.Click += (sender, e) => deleteClickListener(new StocksAdapterClickEventArgs { View = itemView, Position = AdapterPosition });

TextChanged event fires before bound text updates (Winforms .net6.0-winforms)

I want my price calculation to alter as I type, however the calculation is delayed.
My class is as follows
using System;
using System.Windows.Forms;
namespace MyClass
{
public class Model
{
public decimal Cost { get; set; }
public decimal MarkUp { get; set; }
public decimal Price { get; set; }
public decimal CalculatedPrice => Cost * (1 + MarkUp / 100);
}
public partial class FormTest : Form
{
public Model model { get; set; }
public FormTest()
{
InitializeComponent();
model = new Model
{
Price = 0,
MarkUp = 0
};
Calculate();
bs.Add(model);
textBoxCost.DataBindings.Add("Text", bs, "Cost", true, DataSourceUpdateMode.OnPropertyChanged);
textBoxMarkUp.DataBindings.Add("Text", bs, "MarkUp", true, DataSourceUpdateMode.OnPropertyChanged);
textBoxPrice.DataBindings.Add("Text", bs, "Price", true, DataSourceUpdateMode.OnPropertyChanged);
}
private void textBoxCost_TextChanged(object sender, EventArgs e)
{
Calculate();
}
private void Calculate()
{
model.Price = model.CalculatedPrice; // does not have the most up to date value
}
private void textBoxMarkUp_TextChanged(object sender, EventArgs e)
{
Calculate();
}
}
}
When I put a break in Calculate I see that the model has not updated.
What do I need to do?
[Update]
I now have the following:
public class Model : INotifyPropertyChanged
{
private decimal _cost;
public decimal Cost {
get => _cost;
set {
_cost = value;
var args = new PropertyChangedEventArgs(nameof(Cost));
PropertyChanged?.Invoke(this,args );
} }
private decimal _markup;
public decimal MarkUp {
get => _markup;
set {
_markup = value;
var args = new PropertyChangedEventArgs(nameof(MarkUp));
PropertyChanged?.Invoke(this, args);
} }
public decimal Price { get; set; }
public decimal CalculatedPrice => Cost * (1 + MarkUp / 100);
public event PropertyChangedEventHandler? PropertyChanged;
}
and
public partial class FormTest : Form
{
public Model model { get; set; }
public FormTest()
{
InitializeComponent();
}
private void Model_PropertyChanged(object? sender, PropertyChangedEventArgs e) { Calculate(); }
private void AddBinding(TextBox textBox, string dataMember)
{
var binding = new Binding(propertyName: "Text", dataSource: bs, dataMember:dataMember);
// binding.Format += Binding_Format; // yet to do
textBox.DataBindings.Add(binding);
}
private void Calculate() {
model.Price = model.CalculatedPrice;
}
private void FormTest_Load(object sender, EventArgs e)
{
model = new Model { Cost = 0, Price = 0, MarkUp = 0 };
model.PropertyChanged += Model_PropertyChanged;
bs.Add(model);
AddBinding(textBoxCost, "Cost");
AddBinding(textBoxMarkUp, "MarkUp");
AddBinding(textBoxPrice, "Price");
}
}
It works when I press Tab to exit a field.
How can I make it work when KeyUp occurs?
The TextChanged event has the same issue.
I tried issuing bs.EndEdit from within the event code but it did not help.
The following works
private void textBoxCost_KeyUp(object sender, KeyEventArgs e)
{
var tb = sender as TextBox;
model.Cost = Convert.ToDecimal( tb.Text);
Calculate();
}
private void textBoxMarkUp_KeyUp(object sender, KeyEventArgs e)
{
var tb = sender as TextBox;
model.MarkUp = Convert.ToDecimal(tb.Text);
Calculate();
}

UWP c# Dynamically change chart when changing data

I'm trying to make a chart (UWP-c#) which changes dynamically when It's source data changes.
For example:
xaml file:
<StackPanel>
<Button Name="scatterButton" Content="click" Click="ScatterButton_Click" />
<Charting:Chart x:Name="test_chart">
<Charting:ScatterSeries IndependentValuePath="Name" DependentValuePath="Amount" />
</Charting:Chart>
</StackPanel>
c#:
public class SmartPhone
{
public string Name { get; set; }
public int Amount { get; set; }
public int Other { get; set; }
}
public sealed partial class MainPage : Page
{
List<SmartPhone> lstSource = new List<SmartPhone>
{
new SmartPhone() { Name = "IPhone", Amount = 40, Other = 1 },
new SmartPhone() { Name = "Android", Amount = 30, Other = 1 },
new SmartPhone() { Name = "UWP", Amount = 25, Other = 2 }
};
public MainPage()
{
this.InitializeComponent();
}
private void Page_Loaded(object sender, RoutedEventArgs e)
{
LoadChartContent();
}
private void LoadChartContent()
{
(test_chart.Series[0] as ScatterSeries).ItemsSource = lstSource;
}
private void ScatterButton_Click(object sender, RoutedEventArgs e)
{
lstSource[0].Amount = 10;
}
}
The idea is when I click the button "Amount" value change and I want to see it change in the chart.
I tried many packages but this is the only one that really worked for me in UWP. tha NuGet is "WinRTXamlToolkit.Controls.DataVisualization".
Please try to focus on "ScatterSeries" since this is the one I need.
Thanks.
At first you should use ObservableCollection instead of List to automatically notify when items get added or removed.
To notify about changes you have to implement INotifyPropertyChanged and raise PropertyChanged event.
xaml:
<Charting:Chart x:Name="test_chart">
<Charting:ScatterSeries ItemsSource="{x:Bind LstSource}" IndependentValuePath="Name" DependentValuePath="Amount" />
</Charting:Chart>
SmartPhone class example:
public class SmartPhone : INotifyPropertyChanged
{
private int _amount;
public string Name { get; set; }
public int Amount
{
get { return _amount; }
set
{
this._amount = value;
NotifyPropertyChanged();
}
}
public int Other { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MainPage class:
public sealed partial class MainPage : Page
{
public ObservableCollection<SmartPhone> LstSource
{
get { return lstSource; }
}
private ObservableCollection<SmartPhone> lstSource = new ObservableCollection<SmartPhone>
{
new SmartPhone() {Name = "IPhone", Amount = 10, Other = 1},
new SmartPhone() {Name = "Android", Amount = 30, Other = 1},
new SmartPhone() {Name = "UWP", Amount = 25, Other = 2}
};
public MainPage()
{
this.InitializeComponent();
//LoadChartContent();
}
private void ScatterButton_Click(object sender, RoutedEventArgs e)
{
lstSource[0].Amount = 30;
//lstSource.Add(new SmartPhone{Amount = 10, Name = "asd", Other = 2});
}
}
I hope it's all you need.

Get back panel from clipboard

This is my class, I always get a null insted of my panel...
Can someone give me a hint on how to do this?
[Serializable]
public class DragDropBlock : Panel
{
public DragDropBlock()
{
this.MouseDown += new MouseEventHandler(Mouse_Down);
this.MouseUp += new MouseEventHandler(Mouse_Up);
}
void Mouse_Down(object sender, System.Windows.Forms.MouseEventArgs e)
{
Clipboard.SetData("DragDropBlock", this);
}
void Mouse_Up(object sender, System.Windows.Forms.MouseEventArgs e)
{
IDataObject IBlock = Clipboard.GetDataObject();
DragDropBlock Block = (DragDropBlock)IBlock.GetData(typeof(DragDropBlock));
}
}
Given a class:
[Serializable]
class Test
{
public string Data
{
get;
set;
}
}
This works:
Test t = new Test()
{
Data = "DERP!"
};
Clipboard.SetData("Test", t);
Test newT = (Test)Clipboard.GetData("Test");
Console.WriteLine(newT.Data);
And if you want to use data objects:
Test t = new Test()
{
Data = "DERP!"
};
Clipboard.SetDataObject(new DataObject("Test", t));
Test newT = (Test)Clipboard.GetDataObject().GetData("Test");
Console.WriteLine(newT.Data);
The output to both of those is:
DERP!
This is the correction of my class: Working!!!
[Serializable]
class DragBlock
{
public string Data
{
get;
set;
}
}
public class DragDropBlock : Panel
{
DragBlock Block;
public DragDropBlock()
{
this.MouseDown += new MouseEventHandler(Mouse_Down);
this.MouseUp += new MouseEventHandler(Mouse_Up);
Block = new DragBlock()
{
Data = "TEST!"
};
}
void Mouse_Down(object sender, System.Windows.Forms.MouseEventArgs e)
{
Clipboard.SetDataObject(new DataObject("DragBlock", Block));
}
void Mouse_Up(object sender, System.Windows.Forms.MouseEventArgs e)
{
DragBlock newBlock = (DragBlock)Clipboard.GetDataObject().GetData("DragBlock");
Console.WriteLine(newBlock.Data);
}
}

Invoke a delegate on the main thread in a tiered architecture

I have a background process that i want to regularly maintain the state of gps location. I am not clear on how to invoke a delegate on the main thread in the ui layer when the threaded method is in another class. Here is sample code. My form launches the thread on load:
public partial class MainScreen : Form
{
.
. // form stuff
.
private void MainScreen_Load(object sender, EventArgs e)
{
var gpsStatusManager = new GpsStatusManager();
Thread t = new Thread(gpsStatusManager.UpdateLocation);
t.IsBackground = true;
t.Start();
}
delegate void GpsDataParameterDelegate(GpsStatus value);
public void UpdateGpsStatus(GpsStatus value)
{
if (InvokeRequired)
{
// We're not in the UI thread, so we need to call BeginInvoke
BeginInvoke(new GpsDataParameterDelegate(UpdateGpsStatus), new object[] { value });
return;
}
// Must be on the UI thread if we've got this far
gpsStatus.SetGpsStatus(value);
}
}
I have a domain object class for the gps information:
public class GpsStatus
{
public void SetGpsStatus(GpsStatus gpsStatus)
{
Latitude = gpsStatus.Latitude;
Longitude = gpsStatus.Longitude;
CurrentDateTime = gpsStatus.CurrentDateTime;
NumberOfSatellites = gpsStatus.NumberOfSatellites;
TotalNumberSatellites = gpsStatus.TotalNumberSatellites;
}
public float Latitude { get; private set; }
public float Longitude { get; private set; }
public DateTime CurrentDateTime { get; private set; }
public int NumberOfSatellites { get; private set; }
public int TotalNumberSatellites { get; private set; }
}
Then, my manager class where i update status in the secondary thread:
public class GpsStatusManager
{
private GpsStatus _gpsStatus;
public void UpdateLocationx()
{
while (UpdateGpsData())
{
Thread.Sleep(2000);
}
}
private bool UpdateGpsData()
{
SError error;
SGpsPosition gpsPosition;
try
{
if (CApplicationAPI.GetActualGpsPosition(out error, out gpsPosition, true, 0) != 1)
return false;
}
catch (Exception)
{
return false;
}
var numberOfSatellites = gpsPosition.Satellites;
var totalSatellites = gpsPosition.satellitesInfo;
var datetime = gpsPosition.Time;
var lat = gpsPosition.Latitude;
var lon = gpsPosition.Longitude;
_gpsStatus.SetGpsStatus(lat, lon, datetime, numberOfSatellites, totalSatellites);
//How do I invoke the delegate to send the _gpsStatus data to my main thread?
return true;
}
}
Thanks for any assistance.
Here's one way to do it, just off the top of my head:
public class GpsStatusEventArgs : EventArgs
{
public GpsStatus Status { get; private set; }
public GpsStatusEventArgs(GpsStatus status)
{
Status = status;
}
}
public class GpsStatusManager
{
...
public event EventHandler<GpsStatusEventArgs> GpsStatusUpdated;
private void OnGpsStatusUpdated(GpsStatus gpsStatus)
{
EventHandler<GpsStatusEventArgs> temp = GpsStatusUpdated;
if (temp != null)
temp.Invoke(this, new GpsStatusEventArgs(gpsStatus));
}
}
public partial class MainScreen : Form
{
...
private void MainScreen_Load(object sender, EventArgs e)
{
var gpsStatusManager = new GpsStatusManager();
gpsStatusManager.GpsStatusUpdated += new EventHandler<GpsStatusEventArgs>(GpsStatusManager_GpsStatusUpdated);
...
}
private void GpsStatusManager_GpsStatusUpdated(object sender, GpsStatusEventArgs e)
{
UpdateGpsStatus(e.Status);
}
...
}
Then add this to the bottom of UpdateGpsData:
OnGpsStatusUpdated(_gpsStatus);
You should use the SynchronizationContext class.
In the UI thread (in any class), set a field (perhaps static) to SynchronizationContext.Current.
You can then call Send or Post on the saved instance to execute code on the UI thread.
Here is another approach using the ISynchronizeInvoke interface. This is the same pattern the System.Timers.Timer class uses to raise the Elapsed event.
public class GpsStatusManager
{
public ISynchronizeInvoke SynchronizingObject { get; set; }
public event EventHandler Update;
public void UpdateGpsData()
{
// Code omitted for brevity.
OnUpdate(_gpsStatus);
return true;
}
private OnUpdate(GpsStatus status)
{
if (SynchronizingObject != null && SynchronizingObject.IsInvokeRequired)
{
ThreadStart ts = () => { OnUpdate(status); };
SynchronizingObject.Invoke(ts, null);
}
else
{
if (Update != null)
{
Update(this, status);
}
}
}
public class UpdateEventArgs : EventArgs
{
public GpsStatus Status { get; set; }
}
}

Categories