So I seem to be stuck in a loop (no pun intended).
I created my view which consists of a Button control and a TextBlock control.
I have bound my button to a command which invokes a method from my model.
XAML
<Grid>
<TextBlock Text="{Binding CounterValue}" Width=" 100" Height="20"></TextBlock>
<Button Command="{Binding startCommand}" Content="Button" HorizontalAlignment="Left" Margin="472,230,0,0" VerticalAlignment="Top" Width="75"/>
</Grid>
And here is the StartCommand you can ignore this, there is nothing special here
class StartCommand : ICommand
{
private Action _startCommand;
public StartCommand(Action StartCommand)
{
_startCommand = StartCommand;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_startCommand?.Invoke();
}
public event EventHandler CanExecuteChanged;
}
And then we have the model which is a seperate cs file.
class CounterModel
{
static DispatcherTimer calcTimer = new DispatcherTimer();
public StartCommand startCommand { get; } = new StartCommand(Start);
public CounterModel()
{
CounterValue = 10;
}
private static int _counterValue;
public static int CounterValue
{
get { return _counterValue; }
set
{
_counterValue = value;
}
}
public static void Start()
{
//Start some stuff..
Calculate();
}
public static void Calculate()
{
calcTimer.Tick += CalcTimer_Tick;
calcTimer.Interval = TimeSpan.FromSeconds(2);
calcTimer.Start();
}
private static void CalcTimer_Tick(object sender, EventArgs e)
{
PerformanceCounter cpuCounter = new PerformanceCounter
("Process", "% Processor Time", "Firefox", true);
CounterValue = (int)cpuCounter.NextValue();
}
}
My issue right now is that when I click my start button it's not doing anything.. Or well it is but my text property is not updating accoringly, the value is not corresponding to the new value that the timer tick event assigns it.
I tried implementing the interface INotifyPropertyChanged but I cannot do this.
private static int _counterValue;
public static int CounterValue
{
get { return _counterValue; }
set
{
_counterValue = value;
OnPropertyChanged("startCommand");
}
}
Because OnPropertyChanged then needs to be static which again would lead me down a whole new rabbit whole that I shouldnt be down in to begin with.
And I need my properties to be static so I can use them in the Tick event which is called from my Calculate Method which is being called inside Start()
Starts need to be static because I am calling it from alot of others classes.. Either way..
How do I deal with either my properties being static and using INotifyPropertyChanged oooor.. How do I update the TextBlock text value without INotifyPropertyChanged
Without removing the static modifier in Start()
And yes I did set the DataContext
public MainWindow()
{
InitializeComponent();
DataContext = new CounterModel();
}
I understand your scenario here. But isn't the accessibility of your Start() more of a design problem? You can achieve best of both worlds by implementing a singleton pattern to get the value from start method every time you call it, and yet not make Start() static. You can see the example below:
public class CounterModel : INotifyPropertyChanged
{
private static CounterModel _instance = new CounterModel();
public static CounterModel Instance { get { return _instance; } }
private CounterModel()
{
CounterValue = 10;
startCommand = new StartCommand(Start);
}
static DispatcherTimer calcTimer = new DispatcherTimer();
public StartCommand startCommand { get; }
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private int _counterValue;
public int CounterValue
{
get
{
return _counterValue;
}
set
{
_counterValue = value;
NotifyPropertyChanged();
}
}
public void Start()
{
//Start some stuff..
Calculate();
}
public void Calculate()
{
calcTimer.Tick += CalcTimer_Tick;
calcTimer.Interval = TimeSpan.FromSeconds(2);
calcTimer.Start();
}
private void CalcTimer_Tick(object sender, EventArgs e)
{
PerformanceCounter cpuCounter = new PerformanceCounter
("Process", "% Processor Time", "Explorer", true);
CounterValue = (int)cpuCounter.NextValue();
}
}
All the required methods need not be static once the singleton pattern is in place, and you can still always get the common instance of it. Now this is how you can call the instance in your MainWindow.
public MainWindow()
{
InitializeComponent();
DataContext = CounterModel.Instance;
}
In your view you make the following change accordingly,
<TextBlock Text="{Binding CounterValue,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Width="100" Height="20"></TextBlock>
<Button Command="{Binding startCommand}" Content="Button" HorizontalAlignment="Left" Margin="472,230,0,0" VerticalAlignment="Top" Width="75"/>
The ICommand looked fine so I haven't added it as part of answer. Do let me know if the approach works for you.
Related
So I'm making a slot machine in C#. I'm really new to C# and I am really bad at it.
Up to this point my project has been going fine. But now I want to randomize the images shown, when the 'spin' Button is clicked.
I've tried a lot of different things. The solutions I have found are either with the use of a PictureBox or nothing close to what I'm working on.
If someone could take a look at my code and push me in the right direction, I would be really grateful.
Thanks in advance!
using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
namespace Prb.Slot.Machine.Wpf
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
int CoinInsert = 0;
private static Random random;
public enum SlotMachineIcon
{
Banana,
BigWin,
Cherry,
Lemon,
Orange,
Plum,
Seven,
Strawberry,
Watermelon
}
public MainWindow()
{
InitializeComponent();
}
private static void Init()
{
if (random == null) random = new Random();
}
public static int Random(int min, int max)
{
Init();
return random.Next(min, max);
}
void UpdateImage(Image wpfImage, SlotMachineIcon newIcon)
{
DirectoryInfo directoryInfo = new DirectoryInfo(Environment.CurrentDirectory);
directoryInfo = new DirectoryInfo(directoryInfo.Parent.Parent.Parent.Parent.FullName);
Uri uri = new Uri($"{directoryInfo.FullName}/images/{newIcon}.png");
wpfImage.Source = new BitmapImage(uri);
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
lblCoinsInserted.Content = 0;
lblCoinBalance.Content = 0;
lblCoinsWon.Content = 0;
UpdateImage(imgLeft, SlotMachineIcon.Cherry);
UpdateImage(imgMiddle, SlotMachineIcon.Banana);
UpdateImage(imgRight, SlotMachineIcon.Seven);
}
private void btnInsertCoins_Click(object sender, RoutedEventArgs e)
{
int.TryParse(txtInsertCoins.Text, out int InsertCoins);
if (InsertCoins > 0)
{
CoinInsert += int.Parse(txtInsertCoins.Text.ToString());
lblCoinBalance.Content = (int)lblCoinBalance.Content + Convert.ToInt32(txtInsertCoins.Text);
lblCoinsInserted.Content = CoinInsert;
txtInsertCoins.Clear();
}
else
{
MessageBox.Show("Gelieve strikt positieve getallen in te vullen", "Ongeldig aantal munten", MessageBoxButton.OK, MessageBoxImage.Warning);
txtInsertCoins.Clear();
}
}
private void btnSpin_Click(object sender, RoutedEventArgs e)
{
int InsertedCoins = Convert.ToInt32(lblCoinsInserted.Content);
int CoinsBalance = Convert.ToInt32(lblCoinBalance.Content);
/*var v = Enum.GetValues(typeof(SlotMachineIcon));
int number = random.Next(10);*/
if (InsertedCoins == 0 | CoinsBalance == 0)
{
MessageBox.Show("Gelieve eerst munten in te werpen", "Geen munten ingeworpen", MessageBoxButton.OK, MessageBoxImage.Warning);
}
else
{
lblCoinBalance.Content = CoinsBalance - 1;
UpdateImage(imgLeft, SlotMachineIcon.Strawberry);
UpdateImage(imgMiddle, SlotMachineIcon.Watermelon);
UpdateImage(imgRight, SlotMachineIcon.Watermelon);
}
}
}
}
Edit: moved out random declaration as #EmondErno pointed it out.
This method returns a random icon every time you call it:
private Random random = new();
private SlotMachineIcon GetRandomIcon()
{
return (SlotMachineIcon)random.Next(10); //don't forget to update this number if you add or remove icons
}
Then call it in every UpdateImage method like:
UpdateImage(imgLeft, GetRandomIcon());
UpdateImage(imgMiddle, GetRandomIcon());
UpdateImage(imgRight, GetRandomIcon());
You're trying to do everything in the code behind, which is a terrible mistake for many reasons, among which your program will get hard to maintain read and update at some point and you are tight coupling the view and the logic of your program. You want to follow the MVVM pattern and put only in the code behind only the logic of the view (no data).
Also in your code, you're reinventing the updating system that already exists in WPF, you want to use the databinding and WPF updating system and get rid of all "update icon" logic in your program.
This is a ViewModel that you could use (.net 5.0):
public class SlotViewModel: ISlotViewModel, INotifyPropertyChanged
{
private Random _r = new();
private int _slotChoicesCount;
private SlotSet _currentSlotSet;
private ICommand _spinCommand;
public SlotViewModel()
{
_slotChoicesCount = Enum.GetNames(typeof(SlotMachineIcon)).Length;
}
private SlotSet GetNewSet() => new(Enumerable.Range(0,3).Select(o => (SlotMachineIcon)_r.Next(_slotChoicesCount)).ToList());
public SlotSet CurrentSlotSet
{
get => _currentSlotSet;
set
{
if (Equals(value, _currentSlotSet)) return;
_currentSlotSet = value;
OnPropertyChanged();
}
}
public ICommand SpinCommand => _spinCommand ??= new DelegateCommand(s => { CurrentSlotSet = GetNewSet(); }, s => true);
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The most important part is that your ViewModel implements INotifyPropertyChanged. When you uses SpinCommand, it updates the property CurrentSlotSet, and that's all you need to worry about. All the rest is taken care of by the WPF databinding system.
SlotSet is a convenient way to present an immutable result:
public class SlotSet
{
public SlotMachineIcon Left { get; }
public SlotMachineIcon Middle { get; }
public SlotMachineIcon Right { get; }
public SlotSet(IList<SlotMachineIcon> triad)
{
Left = triad[0];
Middle = triad[1];
Right = triad[2];
}
public bool IsWinner => Left == Middle && Middle == Right; // just an example
}
ISlotViewModel is the interface (contract) that your ViewModel satisfies.
public interface ISlotViewModel
{
ICommand SpinCommand { get; }
SlotSet CurrentSlotSet { get; set; }
}
The helper class DelegateCommand:
public class DelegateCommand : ICommand
{
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
public event EventHandler CanExecuteChanged;
public DelegateCommand(Action<object> execute, Predicate<object> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
In your View, XAML part, your only need something as simple as this:
<Button Command="{Binding SpinCommand}">spin</Button>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding CurrentSlotSet.Left}"/>
<Image Source="{Binding CurrentSlotSet.Middle}"/>
<Image Source="{Binding CurrentSlotSet.Right}"/>
</StackPanel>
And in the Windows markup has this:
xmlns:local="clr-namespace:SlotMachine"
d:DataContext="{d:DesignInstance Type=local:SlotViewModel, IsDesignTimeCreatable=True}"
The code behind is as simple as this:
public ISlotViewModel ViewModel
{
get { return (ISlotViewModel)DataContext; }
set { DataContext = value; }
}
public SlotView() // or MainWindow
{
InitializeComponent();
ViewModel = new SlotViewModel();
}
The only thing missing here is to add a converter in each of your <Image, which will convert a SlotMachineIcon value into the image path.
PS: if you don't have resharper, you may need this class too:
[AttributeUsage(AttributeTargets.Method)]
public sealed class NotifyPropertyChangedInvocatorAttribute : Attribute
{
public NotifyPropertyChangedInvocatorAttribute() { }
public NotifyPropertyChangedInvocatorAttribute([NotNull] string parameterName)
{
ParameterName = parameterName;
}
[CanBeNull] public string ParameterName { get; }
}
I'm using Xamarin.Forms with Visual Studio 2017. I have experience in Python, but app development with C# and XAML is completely new to me. Any help would be appreciated.
namespace FirstApp
{
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
private void Calculate
{
Double Num1;
Double Num2;
Answer = (Num1 + Num2).ToString();
}
}
}
<FlexLayout>
<Entry Text="{Binding Num1}"></Entry>
<Entry Text="{Binding Num2}"></Entry>
<Button Text="Go" Clicked="Calculate"></Button>
<Label Text="{Binding Answer}"></Label>
</FlexLayout>
You're mixing things up, your XAML is Binding to properties but you don't have a ViewModel that implements INotifyPropertyChanged to bind to. Also, your Calculate method that is used as a Clicked event handler does not receive the parameters of the EventHandler. If you want to go MVVM, you'll also want to ditch event handling for Commands or Behaviors, but that's another topic.
Here's how to use a ViewModel that Implements INotifyPropertyChanged and use the Calculate method as a Clicked event handler.
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
BindingContext = new ViewModel();
}
private void Calculate(object sender, System.EventArgs e)
{
ViewModel viewModel = BindingContext as ViewModel;
viewModel.Answer = viewModel.Num1 + viewModel.Num2;
}
}
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private double _num1;
public double Num1
{
get { return _num1; }
set
{
if (_num1 == value)
return;
_num1 = value;
OnPropertyChanged(nameof(Num1));
}
}
private double _num2;
public double Num2
{
get { return _num2; }
set
{
if (_num2 == value)
return;
_num2 = value;
OnPropertyChanged(nameof(Num2));
}
}
private double _answer;
public double Answer
{
get { return _answer; }
set
{
if (_answer == value)
return;
_answer = value;
OnPropertyChanged(nameof(Answer));
}
}
protected void OnPropertyChanged(string propertyname) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
this is my first proper C# application that I wrote to help me at work (I'm on helpdesk for an MSP with a passing interest in scripting and code) and I'm using UWP just to make it look pretty without much effort. Our time tracking software is a web service written in ASP.Net so generally the built in timer is fine but it won't survive a browser refresh, so I wrote my own that fits into the format that we need for our tickets.
I have taken some code from other Stack questions and my dad (A C# framework dev for a multinational) helped re-write some of the timer code so it wasn't using stopwatch. He just isn't available to fix this issue at the moment. I do understand how it works now, just not how to debug the issue I'm getting.
It supports multiple timers running at the same time and creating a new timer auto-pauses all others. It handles two time formats, minutes and decimal hours, so that will explain some of the properties you see in the code.
My issue is that when I add a new timer, it pauses all others, but then when I press start on an older timer (Returning back to an earlier ticket) the time instantly jumps up to how long the new timer was running for, with about 10% difference (It's never exactly how long it was running).
This is the class that tracks notes and the current time (Tidied up a bit for neatness):
public sealed class JobTimer:INotifyPropertyChanged
{
private DateTime _created; // When the timer was created
private DateTime _started; // When it was most recently started
private TimeSpan _offset; // The saved value to offset the currently running timer
Timer _swTimer; // The actual tick that updates the screen
public JobTimer() : this(TimeSpan.Zero)
{ }
public JobTimer(TimeSpan offset)
{
_offset = offset;
_created = DateTime.Now;
IsNotLocked = true;
}
// Time in seconds
public string TimeMin => string.Format("{0:00}:{1:00}:{2:00}", ElapsedTime.Hours, ElapsedTime.Minutes, ElapsedTime.Seconds);
// Time in decimal hours
public string TimeDec => string.Format("{0}", 0.1 * Math.Ceiling(10 * ElapsedTime.TotalHours));
public DateTime Created => _created;
public TimeSpan ElapsedTime => GetElapsed();
public void Start()
{
_started = DateTime.Now;
_swTimer = new Timer(TimerChanged, null, 0, 1000);
NotifyPropertyChanged("IsRunning");
}
public void Stop()
{
if (_swTimer != null)
{
_swTimer.Dispose();
_swTimer = null;
}
_offset = _offset.Add(DateTime.Now.Subtract(_started));
NotifyPropertyChanged("IsRunning");
}
private TimeSpan GetElapsed()
{
// This was made as part of my own debugging, the ElaspsedTime property used to just be the if return
if (IsRunning)
{
return _offset.Add(DateTime.Now.Subtract(_started));
}
else
{
return _offset;
}
}
// Updates the UI
private void TimerChanged(object state)
{
NotifyPropertyChanged("TimeDec");
NotifyPropertyChanged("TimeMin");
}
public bool IsRunning
{
get { return _swTimer != null; }
}
public void ToggleRunning()
{
if (IsRunning)
{
Stop();
}
else
{
Start();
}
}
}
This goes into the ViewModel:
public class JobListViewModel
{
private readonly ObservableCollection<JobTimer> _list = new ObservableCollection<JobTimer>();
public ObservableCollection<JobTimer> JobTimers => _list;
public JobListViewModel()
{
AddTimer();
}
public void AddTimer()
{
JobTimer t = new JobTimer();
JobTimers.Add(t);
t.Start();
}
public void PauseAll()
{
foreach(JobTimer timer in JobTimers)
{
timer.Stop();
}
}
// Other functions unrelated
}
And this is the UI button click that adds a new timer
private void AddTimer_Click(object sender, RoutedEventArgs e)
{
// Create JobTimer
ViewModel.PauseAll();
ViewModel.AddTimer();
// Scroll to newly created timer
JobTimer lastTimer = ViewModel.JobTimers.Last();
viewTimers.UpdateLayout();
viewTimers.ScrollIntoView(lastTimer);
}
I realise it's a lot of code to dump into a post but I can't pinpoint where the issue is being caused. I was able to find that something alters the offset when I hit the AddTimer button whether the existing timer is running or not, but I can't find what's altering it.
After building enough other code to support the code you posted, I was able to reproduce your problem.
The issue in your code is that you unconditionally call the Stop() method, whether the timer is already stopped or not. And the Stop() method unconditionally resets the _offset field, whether or not the timer is already running. So, if you add a timer when any other timer is already stopped, its _offset value is incorrectly reset.
IMHO, the right fix is for the Start() and Stop() methods to only perform their work when the timer is in the appropriate state to be started or stopped. I.e. to check the IsRunning property before actually doing the operation.
See below for an actual Minimal, Complete, and Verifiable version of the code you posted, but without the bug.
In addition to fixing the bug, I removed all of the unused elements (that is, all the code that did not appear to be used or discussed in your scenario) and refactored the code so that it is more idiomatic of a typical WPF implementation (see helper/base classes at the end). When I run the program, I am able to start and stop the timer objects without any trouble, even after adding new timers to the list.
Notable modifications:
Use of NotifyPropertyChangedBase class as base class for model classes.
Leverage of said base class features for property change notification, by keeping public properties as simple value-storing properties modified as needed.
Use of ICommand implementation for user actions (i.e. "commands").
Separation of timer-specific start/stop functionality when adding timers from view-specific scrolling-into-view behavior.
Remove time-formatting logic from non-UI model object, and put it in the XAML instead
Use conventional (and more readable) - and + operators for DateTime and TimeSpan math
JobTimer.cs:
class JobTimer : NotifyPropertyChangedBase
{
private DateTime _started; // When it was most recently started
private TimeSpan _offset; // The saved value to offset the currently running timer
Timer _swTimer; // The actual tick that updates the screen
private readonly DelegateCommand _startCommand;
private readonly DelegateCommand _stopCommand;
public ICommand StartCommand => _startCommand;
public ICommand StopCommand => _stopCommand;
public JobTimer() : this(TimeSpan.Zero)
{ }
public JobTimer(TimeSpan offset)
{
_offset = offset;
_startCommand = new DelegateCommand(Start, () => !IsRunning);
_stopCommand = new DelegateCommand(Stop, () => IsRunning);
}
private TimeSpan _elapsedTime;
public TimeSpan ElapsedTime
{
get { return _elapsedTime; }
set { _UpdateField(ref _elapsedTime, value); }
}
public void Start()
{
_started = DateTime.UtcNow;
_swTimer = new Timer(TimerChanged, null, 0, 1000);
IsRunning = true;
}
public void Stop()
{
if (_swTimer != null)
{
_swTimer.Dispose();
_swTimer = null;
}
_offset += DateTime.UtcNow - _started;
IsRunning = false;
}
private TimeSpan GetElapsed()
{
return IsRunning ? DateTime.UtcNow - _started + _offset : _offset;
}
// Updates the UI
private void TimerChanged(object state)
{
ElapsedTime = GetElapsed();
}
private bool _isRunning;
public bool IsRunning
{
get { return _isRunning; }
set { _UpdateField(ref _isRunning, value, _OnIsRunningChanged); }
}
private void _OnIsRunningChanged(bool obj)
{
_startCommand.RaiseCanExecuteChanged();
_stopCommand.RaiseCanExecuteChanged();
}
}
MainViewModel.cs:
class MainViewModel : NotifyPropertyChangedBase
{
public ObservableCollection<JobTimer> JobTimers { get; } = new ObservableCollection<JobTimer>();
public ICommand AddTimerCommand { get; }
public MainViewModel()
{
AddTimerCommand = new DelegateCommand(_AddTimer);
_AddTimer();
}
private void _AddTimer()
{
foreach (JobTimer timer in JobTimers)
{
timer.Stop();
}
JobTimer t = new JobTimer();
JobTimers.Add(t);
t.Start();
}
}
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MainViewModel model = (MainViewModel)DataContext;
model.JobTimers.CollectionChanged += _OnJobTimersCollectionChanged;
}
private void _OnJobTimersCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
ObservableCollection<JobTimer> jobTimers = (ObservableCollection<JobTimer>)sender;
// Scroll to newly created timer
JobTimer lastTimer = jobTimers.Last();
listBox1.ScrollIntoView(lastTimer);
}
}
MainWindow.xaml:
<Window x:Class="TestSO46416275DateTimeTimer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:l="clr-namespace:TestSO46416275DateTimeTimer"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<l:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type l:JobTimer}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding ElapsedTime, StringFormat=hh\\:mm\\:ss}"/>
<Button Content="Start" Command="{Binding StartCommand}"/>
<Button Content="Stop" Command="{Binding StopCommand}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Content="Add Timer" Command="{Binding AddTimerCommand}" HorizontalAlignment="Left"/>
<ListBox x:Name="listBox1" ItemsSource="{Binding JobTimers}" Grid.Row="1"/>
</Grid>
</Window>
NotifyPropertyChangedBase.cs:
class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void _UpdateField<T>(ref T field, T newValue,
Action<T> onChangedCallback = null,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return;
}
T oldValue = field;
field = newValue;
onChangedCallback?.Invoke(oldValue);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
DelegateCommand.cs:
class DelegateCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
public DelegateCommand(Action execute) : this(execute, null)
{ }
public DelegateCommand(Action execute, Func<bool> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute();
}
public void Execute(object parameter)
{
_execute();
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
I am making a display of time/clock as a user control (ClockControl) on a page, the actual time model is driven from a DateTime object from another class (ClockTime). I have 2 textblocks on the ClockControl:
Textblock 'Second_update_by_binding' is bound to a dependency property 'Second_binded' which in turn is bound to model ClockTime 'Second'.
Textblock 'Second_update_by_manipulating' is updated by manipulating the value of the model ClockTime 'Second' so that it adds '0' at front if the 'Second' is only 1 digit (or less than 10)
I managed to achieve what I want regardless if its the best approach. However, I have come across a few questions that I don't quite understand why they happen. In particular, its the logic behind the code inside the getter/setter within the dependency property in the Clock user control that I am most confused of.
MainPage.xaml:
<Page x:Class="App1.MainPage" ...">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<local:ClockControl x:Name="useControl"
Second_binded="{Binding Second}"
Second_manipulated="{Binding Second}" />
</Grid>
</Page>
MainPage.cs:
public sealed partial class MainPage : Page
{
ClockTime clock;
DispatcherTimer Timer;
public MainPage()
{
clock = new ClockTime();
this.InitializeComponent();
this.DataContext = clock;
// I am adding this DispatchTimer to force the update of the text
// 'Second_update_by_manipulating' on the ClockControl since the
// Binding of Second_manipulated doesnt work
Timer = new DispatcherTimer { Interval = new TimeSpan(0, 0, 1) };
Timer.Tick += Timer_Tick;
Timer.Start();
}
private void Timer_Tick(object sender, object e)
{
useControl.Second_manipulated = clock.Second.ToString();
}
}
ClockTime model:
class ClockTime : INotifyPropertyChanged
{
public ClockTime()
{
var Timer = new DispatcherTimer { Interval = new TimeSpan(0, 0, 1) };
Timer.Tick += Timer_Tick;
Timer.Start();
}
private void Timer_Tick(object sender, object e)
{
Second = DateTime.Now.Second;
}
//===================================================
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
//===================================================
private int _second;
public int Second
{
get { return this._second; }
set
{
if (value != this._second)
{
this._second = value;
NotifyPropertyChanged("Second");
}
}
}
}
ClockControl.xaml:
<UserControl x:Class="App1.ClockControl" ...>
<Grid>
<StackPanel>
<TextBlock x:Name="Second_update_by_manipulating" />
<TextBlock x:Name="Second_update_by_binding" Text="{Binding Second_binded}" />
</StackPanel>
</Grid>
</UserControl>
ClockControl.cs:
public sealed partial class ClockControl : UserControl
{
public ClockControl()
{
this.InitializeComponent();
(this.Content as FrameworkElement).DataContext = this;
}
//==================================
public String Second_binded
{
get {
return (String)GetValue(Second_bindedProperty);
}
set {
Debug.WriteLine(" in Second_binded ");
SetValue(Second_bindedProperty, value);
}
}
public static readonly DependencyProperty Second_bindedProperty =
DependencyProperty.Register(
"Second_binded",
typeof(string),
typeof(ClockControl),
new PropertyMetadata(""));
//==================================
public String Second_manipulated
{
get
{
return (String)GetValue(Second_manipulatedProperty);
}
set
{
Second_update_by_manipulating.Text = (Convert.ToInt32(value)<10) ? "0"+value : value;
Debug.WriteLine(" in Second_manipulated ");
SetValue(Second_manipulatedProperty, value);
}
}
public static readonly DependencyProperty Second_manipulatedProperty =
DependencyProperty.Register(
"Second_manipulated",
typeof(string),
typeof(ClockControl),
new PropertyMetadata(""));
//==================================
}
so here are my questions:
Why the debugging code Debug.WriteLine(" in Second_binded "); within the setter of Second_binded dependency property in ClockControl is never called when the 'Second' in model ClockTime is being updated.
The code Debug.WriteLine(" in Second_manipulated "); within the setter of Second_manipulated dependency property in ClockControl is called and the code Value_1.Text = (Convert.ToInt32(value)<10) ? "0"+value : value; is executed to change the text on the ClockControl, but it only works after adding another DispatchTimer withtin the MainPage.cs to force the code useControl.Second_manipulated = clock.Second.ToString(); to update the time. Why I have to do it this way to update Second_manipulated, even though I have already set Second_manipulated binded to Second in the MainPage.xaml?
Any ideas and comments to enlighten my knowledge on C# is very welcomed.
thanks
Ken
If you want to track changes in a DependencyProperty, you have to register a PropertyChangedCallback handler. The property setter is not triggered by the system when the value of a binding is updated.
public static readonly DependencyProperty Second_bindedProperty =
DependencyProperty.Register(
"Second_binded",
typeof(string),
typeof(ClockControl),
new PropertyMetadata("", PropertyChangedCallback));
private static void PropertyChangedCallback(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
Debug.WriteLine(" in Second_binded callback");
}
The reason why your setter is hit in the second property is because you force it yourself with useControl.Second_manipulated = clock.Second.ToString();, which is not the correct way of using bindings.
I got some problem in showing download percentage in GridView of WCF. I used MVVM pattern.
Here is my background worker in application start:
public partial class MainWindow : Window
{
public MainWindow()
{
Overall.EverythingOk = "Nothing";
InitializeComponent();
//IRepo repo = new Repo();
ViewModel.MainWindowsViewModel viewModel = new ViewModel.MainWindowsViewModel();
this.DataContext = viewModel;
BackGroundThread bgT = new BackGroundThread();
bgT.bgWrk.RunWorkerAsync();
}}
Here is the DoWork function in BackGroundTHread class
public void bw_DoWork(object sender, DoWorkEventArgs e)
{
if (!Overall.stopStatus)
{
for (int i=0; i < 10000; i++)
{
Overall.PercentageDwnd = i;
Overall.caseRefId = "999999";
if (i == 9998)
{
i = 1;
}
}
}
}
Overall.PercentageDwnd and Overall.caseRefId are static variable (you can call from everywhere in the application) and always update until the background worker completed. I got another ViewModel called TestViewModel and here it is.
public class TestViewModel:BindableBase
{
private String _UpdatePer=Overall.PercentageDwnd.ToString();
public String UpdatePercentage
{
get { return _UpdatePer; }
set { SetProperty(ref _UpdatePer, value); }
}
private ObservableCollection _ViewAKA = new ObservableCollection();
private tblTransaction model;
public TestViewModel(tblTransaction model)
{
// TODO: Complete member initialization
}
public ObservableCollection ViewAKA
{
get { return _ViewAKA; }
set { SetProperty(ref _ViewAKA, value); }
}
}
I bind with TestView.xaml file
<Window x:Class="EmployeeManager.View.TestView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TestView" Height="359.774" Width="542.481">
<Grid Margin="0,0,2,0">
<Label Content="{Binding UpdatePercentage,UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Background="Red" Foreground="White" Margin="130,86,0,0" VerticalAlignment="Top" Width="132" Height="39">
</Label>
</Grid>
</Window>
There is no real time update at Label even though I bind UpdatePercentage to it. How can I update real time to label?
The problem is that you are updating the static properties, which are not bound to anything. You need to update and raise the property changed notification for the properties which are bound to the label controls, i.e. UpdatePercentage
Can you pass the TestViewModel instance into the RunWorkerAsync call?
bgT.bgWrk.RunWorkerAsync(testViewModel);
And then access in the DoWork event handler:
public void bw_DoWork(object sender, DoWorkEventArgs e)
{
if (!Overall.stopStatus)
{
var viewModel = e.Argument as TestViewModel;
for (int i=0; i < 10000; i++)
{
Overall.PercentageDwnd = i;
viewModel.UpdatePercentage = i;
Overall.caseRefId = "999999";
if (i == 9998)
{
i = 1;
}
}
}
}
Here is answer link:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/02a7b9d1-1c26-4aee-a137-5455fee175b9/wpf-percentage-status-shown-in-label-mvvm?forum=wpf
i need to trigger when the Overall.PercentageDwnd property changes.
Edited
In Overall Class:
public class Overall
{
private static int _percentage;
public static int PercentageDwnd
{
get { return _percentage; }
set
{
_percentage = value;
//raise event:
if (PercentageDwndChanged != null)
PercentageDwndChanged(null, EventArgs.Empty);
}
}
public static string caseRefId { get; set; }
public static bool stopStatus { get; set; }
public static event EventHandler PercentageDwndChanged;
}
In TestViewModel:
public class TestViewModel : BindableBase
{
private String _UpdatePer = Overall.PercentageDwnd.ToString();
public String UpdatePercentage
{
get { return _UpdatePer; }
set { SetProperty(ref _UpdatePer, value); }
}
public TestViewModel(tblTransaction model)
{
Overall.PercentageDwndChanged += Overall_PercentageDwndChanged;
// TODO: Complete member initialization
}
private void Overall_PercentageDwndChanged(object sender, EventArgs e)
{
this.UpdatePercentage = Overall.PercentageDwnd.ToString();
}
}
Since you have bound the TextBlock in the view to the UpdatePercentage source property, you need to set this one and raise the PropertyChanged event whenever you want to update the Label in the view. This means that you need to know when the Overall.PercentageDwnd property changes.
Credit to
Magnus (MM8)
(MCC, Partner, MVP)
Thanks All