INotifyPropertyChanged implemented, but textblock only updates once - c#

ANSWERED BY #grek40
I have implemented INotifyPropertyChanged in my project and for everything else it is working fine, but for this one variable-bound textblock it is only updating once on the main window load event.
I'm probably just missing some little detail somewhere, please help!
MainWindow.xaml
<TextBlock HorizontalAlignment="Left" Margin="317,161,0,0"
TextWrapping="Wrap" Text="{Binding ish.IsDoingWork, Mode=OneWay,
UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top"/>
<Button Command="{Binding hksVm.HentKundeStatus}" Content="Invoke"
HorizontalAlignment="Left" Margin="704,139,0,0" VerticalAlignment="Top"
Width="75"/>
MainWindow.xaml.cs (setting data context)
public MainWindow()
{
if (!ValidationHandler.GrantAccess().Equals(3))
{
InitializeComponent();
DataContext = new
{
hksVm = new HentKundeStatusVm(),
ish = new InvocationServiceHandler()
};
}
else
{
Close();
}
}
ViewModel.cs
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Input;
namespace MyNamespace
{
public class HentKundeStatusVm : IViewModel
{
private ICommand _hentKundeStatus;
private readonly InvocationServiceHandler _invocationServiceHandler = new InvocationServiceHandler();
public ICommand HentKundeStatus => HentKundeStatusCommand();
public ICommand HentKundeStatusCommand()
{
if (ValidationHandler.GrantAccess() < 2)
{
return _hentKundeStatus ?? (_hentKundeStatus = new RelayCommand(param =>
ElapsedTime = _invocationServiceHandler.ExecuteAndTimeAction(
() =>
{
//web API kaldes asynkront - husk: using System.Net.Http;
using (var client = new HttpClient().GetAsync("API-url"))
{
client.Result.Content.ReadAsStringAsync();
}
}, AntalKald)));
}
return null;
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
}
InvocationServiceHandler.cs
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading.Tasks;
using App005_WebServiceTestingTool_Domain.Annotations;
namespace App005_WebServiceTestingTool_Domain.Handlers
{
public class InvocationServiceHandler : INotifyPropertyChanged
{
// This works on the main_window load event and set the textblock in the view
private string _isDoingWork = "Currently not working";
public string IsDoingWork
{
get => _isDoingWork;
set
{
_isDoingWork = value;
NotifyPropertyChanged(nameof(IsDoingWork));
}
}
/// <summary>
/// Method that invokes action parameter x times in multiple threads (parallel) and returns the elapsed time
/// </summary>
/// <param name="action"></param>
/// <param name="antalKald"></param>
/// <returns></returns>
public string ExecuteAndTimeAction(Action action, string antalKald)
{
// Here is set the bound variable, and if I debug I can see it getting set to Working...
IsDoingWork = "Working...";
var sw = new Stopwatch();
sw.Start();
for (int i = 0; i < Convert.ToInt32(antalKald); i++)
{
action.Invoke();
}
sw.Stop();
// Here I am resetting the variable and again in debug I can see it change, but nothing happens in the view
IsDoingWork = "";
return $"Elapsed time: {sw.Elapsed}";
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
public void NotifyPropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
}

Basically, you have two instances of InvocationServiceHandler. One inside the DataContext as ish = new InvocationServiceHandler() and the other inside MyViewModel as private readonly InvocationServiceHandler _invocationServiceHandler = new InvocationServiceHandler();
So ish.IsDoingWork is displayed and something like hksVm._invocationServiceHandler.IsDoingWork is updated. It's not really clear since HentKundeStatusVm and MyViewModel are not really the same thing in the question.
It should be possible to fix this situation by Constructor Injection of the service handler:
public class HentKundeStatusVm : IViewModel
{
private readonly InvocationServiceHandler _invocationServiceHandler;
public HentKundeStatusVm(InvocationServiceHandler ish)
{
_invocationServiceHandler = ish;
}
// the other stuff
}
Then
// In the MainWindow constructor
var ishInstance = new InvocationServiceHandler();
DataContext = new
{
hksVm = new HentKundeStatusVm(ishInstance),
ish = ishInstance
};
Then you have the same handler instance available for binding and for execution.

Your logic is fine , everything looks properly attached and the values are updated as expected . The only problem is your UI isn't updating it because you're executing the For loop on the main thread which unfortunately is blocking all your UI updates.
So You can
Run ExecuteAndTimeAction as a background operation using, Backgroundworker/[Task Library][1].
2.Use Dispatcher to flush your UI messages, eg:
/// <summary>
/// Enters the message loop to process all pending messages down to the specified
/// priority. This method returns after all messages have been processed.
/// </summary>
/// <param name="priority">Minimum priority of the messages to process.</param>
public static void DoEvents(DispatcherPriority priority = DispatcherPriority.Background)
{
DispatcherFrame frame = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(
priority,
new DispatcherOperationCallback(ExitFrame), frame);
Dispatcher.PushFrame(frame);
}
private static object ExitFrame(object f)
{
((DispatcherFrame)f).Continue = false;
return null;
}
And Call
/// <summary>
/// Method that invokes action parameter x times in multiple threads (parallel) and returns the elapsed time
/// </summary>
/// <param name="action"></param>
/// <param name="antalKald"></param>
/// <returns></returns>
public string ExecuteAndTimeAction(Action action, string antalKald)
{
// Here is set the bound variable, and if I debug I can see it getting set to Working...
IsDoingWork = "Working...";
DoEvent();//flushes the UI msg queue
var sw = new Stopwatch();
sw.Start();
for (int i = 0; i < Convert.ToInt32(antalKald); i++)
{
action.Invoke();
}
sw.Stop();
// Here I am resetting the variable and again in debug I can see it change, but nothing happens in the view
IsDoingWork = "";
DoEvent();//flushes the UI msg queue
return $"Elapsed time: {sw.Elapsed}";
}
The second approach is a hack and it will still freeze
the UI but will get the job done for you.
I suggest you go for the first approach,
It is way better but takes effort to implement.

Related

How can I run 2 commands within 1 button

I'm developing a game that wants to check wether a certain puzzle is solved or not. This puzzle is inside a UserControl, and if it is solved I want to show another UserControl.
The method to change one UserControl1 to UserControl2 is inside the MainWindowViewModel, and I have been able to solve this issue with this command:
Button 1:
<Button Content="Grid" Command="{Binding DataContext.NavigateCommand, RelativeSource={RelativeSource AncestorType={x:Type local:MainWindow}}, Mode=OneWay}" CommandParameter="5" />
The logic thing would be to pass the Puzzle as a variable and let the second UserControl check if the Puzzle is solved, but given how this command is coded within the MainViewModel that isn't possible. The MainViewModel looks like this:
public class MainViewModel : ViewModelBase, INotifyPropertyChanged
{
private IPage content;
public IPage Content { get => content; protected set => Set(ref content, value); }
public RelayCommand<int> NavigateCommand => new RelayCommand<int>(Navigate);
private readonly Dictionary<int, Lazy<IPage>> pages = new Dictionary<int, Lazy<IPage>>
{
[1] = new Lazy<IPage>(() => new IntroViewModel()),
[2] = new Lazy<IPage>(() => new ChoosePuzzleViewModel()),
[3] = new Lazy<IPage>(() => new GameViewModel(5)),
[4] = new Lazy<IPage>(() => new GameViewModel(7)),
[5] = new Lazy<IPage>(() => new GameViewModel(9)),
[6] = new Lazy<IPage>(() => new ResultViewModel())
};
public MainViewModel() => Navigate(1);
public void Navigate(int value) => Content = pages[value].Value;
}
So the solution I am trying to apply is to let the PuzzleViewModel check wether the puzzle is correct with a Button, just like this:
Button 2:
<Button Name="SolveButton" Content="Check" Height="80" Width="100" Command="{Binding VMPuzzle.SolvedCheck}" />
And depending on the result this Command returns, run the Button 1 command.
It is not necessary that the second Command is in a Button, that is for illustration purposes.
So the question is, would it be possible for one Button to run two commands, one after the other?
You could execute the "NavigateCommand" from the VMPuzzle ViewModel if you have a reference to your MainViewModel class. You can fire the command with
NavigateCommand.Execute();
The implementation of the VMPuzzle class would be useful to see.
Example:
public class VMPuzzle
{
MainViewModel _mainViewModel;
public VMPuzzle(MainViewModel mainViewModel)
{
_mainViewModel = mainViewModel;
}
private void CallExecuteCommandInMainViewModel()
{
_mainViewModel.NavigateCommand.Execute();
}
}
CallExecuteCommandInMainViewModel();
would be called from the SolvedCheck Command in your VMPuzzle Class or you directly fire it with
_mainViewModel.NavigateCommand.Execute();
without a method.
I assume you have direct access to your MainViewModel so you can directly call NavigateCommand.Execute(); in your SolvedCheckCommand.
Because of the Binding i see in your example:
Command="{Binding VMPuzzle.SolvedCheck}"
Alternatively you could work with a EventHandler like in this example "i didnt implement the Commands itself in this example but the execute void:
EDIT: added summaries for clarification purposes only
using System;
namespace Testproject
{
public class MainViewModel
{
public MainViewModel()
{
// Subscribe to the event in VMPuzzle but dont forget to unsuscribe later if needed!
var vm_Puzzle = new VMPuzzle();
vm_Puzzle.SolvedCheck += Vm_Puzzle_SolvedCheck;
}
/// <summary>
/// <see cref="VMPuzzle.Execute_CommandSolvedCheck(object)"/>
/// got executed so we want to do something after that.
/// In this example we execute another command
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Vm_Puzzle_SolvedCheck(object sender, EventArgs e)
{
// Fire the Navigate Command
// NavigateCommand.Execute();
}
}
public class VMPuzzle
{
public VMPuzzle()
{
doStuff();
}
/// <summary>
/// The Method which gets executed from the Command
/// </summary>
/// <param name="Parameter"></param>
private void Execute_CommandSolvedCheck(object Parameter)
{
OnSolvedCheck(EventArgs.Empty);
}
/// <summary>
/// Execute_CommandSolvedCheck is completely executed, inform subscribers if there are some
/// </summary>
/// <param name="e"></param>
private void OnSolvedCheck(EventArgs e)
{
SolvedCheck?.Invoke(this, new EventArgs());
}
/// <summary>
/// Just a standard Method we fire
/// </summary>
public void doStuff()
{
OnSolvedCheck(EventArgs.Empty);
}
public event EventHandler SolvedCheck;
}
}

Single-Variable Binding in Win Forms: What am I doing wrong?

I've been trying to make binding work for two labels in a WinForm, but I can't seem to figure out what I'm doing wrong. Currently, I'm implementing the INotifyPropertyChanged interface, and rigged it to a couple of properties within a Form. The current classes this affects are SessionForm.cs, the actual form, and Session.cs, the place where I keep all the information of the program. The labels in question, which are not mentioned in either class, are L_No, which holds the numerical reference of the Note in the musical Scale, and L_Note, which holds the visceral Note value (e.g. C, C#, etc.).
Allow me to explain what everything does within the classes. The program is designed to test your scale knowledge by asking you, based on the your chosen scale, what nth note of the scale is. You use the buttons on the form to make your choice.
These choices are recorded within the Session class, which has been edited to make this more succinct. The array of integers holds the indices of the notes in relation to the scale array, which is in the Scale object. For example, a typical Note array may hold these values: {1,3,0,2,6,1,3,...}. By using the array in the Scale object as a reference, these would translate into musical notes (e.g. D, F, C, E, B, D, F,...). The player's choices are stored within an array of NoteData objects.
In SessionForm.cs I'm manipulating that information over time. Each time a choice is or isn't made (depending on whether or not they attempted to guess in time), the value of the two Labels are changed: L_No, and L_Note. These two Labels are manipulated by the variables NoteIndex and LastNote, respectively. When these change in value, NotifyPropertyChanged occurs, and then the Labels should be updated...but they're not doing so.
Now, in the design section of the form, in the Properties window, I set up the Text property of each Label to be bound to their respective variables within the form, and set to update upon Property Change, but nothing seems to be working.
So what am I doing wrong?
Session.cs:
public class Session
{
public struct NoteData
{
public int Note;
public bool Correct;
public int GuessTime;
}
public Scale Scale;
/// <summary>
/// Holds the notes for one game
/// </summary>
public int[] Notes { get; private set; }
public NoteData[] Data { get; private set; }
/// <summary>
/// Creates a Session
/// </summary>
/// <param name="difficulty">The difficult of the session, refer to the Resources Class for determination.</param>
/// <param name="scale_used">The scale to be used. Refer to the Resources Class for determination.</param>
/// <param name="notes">The notes being used within this Session</param>
public Session(Resources.Difficulties difficulty, Scale scale_used, int[] notes)
{
ID = DateTime.Now;
Diff = difficulty;
Scale = scale_used;
Notes = notes;
Data = new NoteData[notes.Length];
internalIndex = 0;
}
/// <summary>
/// Stores Note input for each guessed
/// </summary>
/// <param name="index">The index of the note the player is currently on</param>
/// <param name="correct">Was the guess correct?</param>
/// <param name="remaining_time">How long did it take for them to guess?</param>
public void StoreNoteInput(int index, bool correct, int remaining_time)
{
if (internalIndex < Data.Length)
Data[internalIndex++] = new NoteData(index, remaining_time, correct);
}
}
SessionForm.cs:
public partial class SessionForm : Form, INotifyPropertyChanged
{
public Session curSession { get; private set; }
Resources.Notes last_note;
/// <summary>
/// The note index number in relation to the scale
/// </summary>
public int NoteIndex
{
get
{ return note_index; }
private set
{
if (note_index != value)
{
note_index = value;
NotifyPropertyChanged("NoteIndex");
}
}
}
/// <summary>
/// Represents the previous note being tested
/// </summary>
public Resources.Notes LastNote
{
get
{
return last_note;
}
private set
{
if (last_note != value)
{
last_note = value;
NotifyPropertyChanged("LastNote");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void TickDownTimer_Tick(object sender, EventArgs e)
{
remainingTime -= countingDown ? 1000 : 100;
if (remainingTime == 0)
{
if (countingDown)
{
countingDown = false;
TickDownTimer.Interval = 100;
}
if (curIndex > 0)
{
//you ran out of time on the last note
RecordNoteInput(curIndex - 1, false);
}
NextNote();
}
SetTimerText();
}
private void RecordNoteInput(int index, bool correct)
{
curSession.StoreNoteInput(index, correct, remainingTime);
NoteIndex = curSession.Notes[curIndex - 1];
LastNote = curSession.Scale.Notes[NoteIndex];
L_Note.ForeColor = correct ? Color.Green : Color.Red;
}
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
UPDATE: Here's the binding code that comes from SessionForm.Designer.cs:
this.sessionFormBindingSource1 = new System.Windows.Forms.BindingSource(this.components);
this.sessionFormBindingSource2 = new System.Windows.Forms.BindingSource(this.components);
this.sessionFormBindingSource = new System.Windows.Forms.BindingSource(this.components);
//
// L_Note
//
this.L_Note.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.sessionFormBindingSource1, "LastNote", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged, "C"));
this.L_Note.Text = " ";
//
// L_No
//
this.L_No.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.sessionFormBindingSource2, "NoteIndex", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged, "1", "N0"));
this.L_No.Text = " ";
The problem is the way you call NotifyPropertyChanged:
NotifyPropertyChanged("note_index");
and
NotifyPropertyChanged("last_note");
Just remove the strings from the calls like this
NotifyPropertyChanged();
and everything should be fine.
Edit: If it's not, then your bindings are not initialized correctly. Prove:
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Forms;
namespace Tests
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new TestForm());
}
}
class TestForm : Form, INotifyPropertyChanged
{
public TestForm()
{
var label = new Label { Parent = this, Left = 16, Top = 16, AutoSize = false, BorderStyle = BorderStyle.FixedSingle };
label.DataBindings.Add("Text", this, "NoteIndex");
var timer = new Timer { Interval = 200, Enabled = true };
timer.Tick += (sender, e) => NoteIndex = (NoteIndex + 1) % 10;
}
int note_index;
public int NoteIndex
{
get { return note_index; }
private set
{
if (note_index != value)
{
note_index = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}

MVVM WPF BackgroundWorker and UI update

i'm learning MVVM pattern with wpf and i'm trying to create a simple splashscreen for loading applications.
I have a simple class called Loading with two property which are bounded to my interface.
public class Loading : INotifyPropertyChanged
{
/// <summary>
/// Define current status value from 0 to 100.
/// </summary>
private int _currentStatus;
/// <summary>
/// Define current status text.
/// </summary>
private string _textStatus;
/// <summary>
/// Define constructor.
/// </summary>
public Loading(int status, string statusText)
{
_currentStatus = status;
_textStatus = statusText;
}
public int CurrentStatus
{
get { return _currentStatus; }
set
{
_currentStatus = value;
OnPropertyChanged("CurrentStatus");
}
}
public string TextStatus
{
get { return _textStatus; }
set
{
_textStatus = value;
OnPropertyChanged("TextStatus");
}
}
#region Interfaces
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
From my ctor ViewModel i instanciate this model
Loading = new Loading(0, "Loading...");
and run a new thread calling the function GetSystemInfo() which perform some stuff in order to load some information.
Thread systemInfo = new Thread(GetSystemInfo);
systemInfo.IsBackground = true;
systemInfo.Start();
I'm updating the ui from GetSystemInfo() with
Loading.TextStatus = "Loading User Information...";
Loading.CurrentStatus = 50;
So far so good.. the thread is correctly updating the ui but the problem is that i wish to close this splashcreen and open a new window when the loading is complete but i'm unable to check if the thread is complete or at least i don't found a way to do that.
Is there any way i can solve this problem?
Thanks.
You achieve this rather easily by using the Task class (via Task Parallel Library) with a combination of async-await.
What happens when you await on a Task is that the control is yielded back to the caller. In your case, the caller comes from the UI thread, so the control will return to the UI message loop, keeping you app responsive. Once the thread finishes it's work, it will return to the next line after the await, where you can then open the splash screen.
It will look like this:
public async void MyEventHandler(object sender, EventArgs e)
{
await Task.Run(() => GetSystemInfo());
// Here, you're back on the UI thread.
// You can open a splash screen.
}

WPF DependencyProperty throws InvalidOperationException

I am trying to set a dependency property which is updated by a WCF callback thread.
There is a ProgressBar on MainWindow.xaml that is bound to this property:
MainWindow.xaml
<ProgressBar Name="ProgressBar" Value="{Binding Progress, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
MainWindow has an instance of DemoModule, which is defined as:
DemoModule.xaml.cs
/// <summary>
/// Interaction logic for DemoModule.xaml
/// </summary>
public partial class DemoModule : UserControl, INotifyPropertyChanged
{
public static readonly DependencyProperty ProgressProperty = DependencyProperty.Register("Progress", typeof(int), typeof(DemoModule));
public event ProgressEventHandler ProgressChanged;
public event PropertyChangedEventHandler PropertyChanged;
public int Progress
{
get { return (int)GetValue(ProgressProperty); }
set { SetValue(ProgressProperty, value); } // setter throws InvalidOperationException "The calling thread cannot access this object because a different thread owns it"
}
/// <summary>
/// Initializes a new instance of the <see cref="DemoModule" /> class.
/// </summary>
public DemoModule()
{
InitializeComponent();
ProgressChanged += OnProgressChanged;
}
/// <summary>
/// Called when [progress changed].
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="args">The <see cref="ProgressChangedEventArgs" /> instance containing the event data.</param>
public void OnProgressChanged(object sender, ProgressChangedEventArgs args)
{
Debug.WriteLine("Current Thread: {0}", Thread.CurrentThread.ManagedThreadId);
Debug.WriteLine("Current Dispatcher Thread: {0}", Application.Current.Dispatcher.Thread.ManagedThreadId);
if (ProgressChanged == null) return;
Debug.WriteLine("ProgressChangedEventArgs.Current: " + args.Current);
Progress = Convert.ToInt32(args.Current * 100);
OnPropertyChanged("Progress");
}
/// <summary>
/// Called when [property changed].
/// </summary>
/// <param name="propertyName">Name of the property.</param>
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
Trace.WriteLine("Property " + propertyName + " changed. Value = " + Progress);
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
The Progress.set() is throwing an exception because of the thread affinity.
How can I fix this?
Update 1
This is allegedly thread safe, but has no effect:
public int Progress
{
get
{
return Dispatcher.Invoke((() => (int)GetValue(ProgressProperty)));
}
set
{
Dispatcher.BeginInvoke((Action)(() => SetValue(ProgressProperty, value)));
}
}
Update 2
My DemoModule.xaml.cs has a reference to a client library which implements the WCF callback method OnUpdateProgress:
InstallerAgentServiceClient.cs
public void OnUpdateProgress(double progress)
{
//Debug.WriteLine("Progress: " + progress*100 + "%");
var args = new ProgressChangedEventArgs(progress, 1, "Installing");
_installModule.OnProgressChanged(this, args);
}
The _installModule object above is the instance of DemoModule.
Update 3
After removing the [CallBackBehavior] attribute from the WCF client library, there no longer seems to be thread synchronization issues. I can update the progress bar in the MainWindow as follows:
DemoModule.xaml.cs
public void OnProgressChanged(object sender, ProgressChangedEventArgs args)
{
Progress = Convert.ToInt32(args.Current * 100);
var progressBar = Application.Current.MainWindow.FindName("ProgressBar") as ProgressBar;
if (progressBar != null)
progressBar.Value = Progress;
}
You need to update your DepedencyProperty via the UI Thread. Use:
Application.Current.Dispatcher.BeginInvoke(Action)
Or:
Application.Current.Dispatcher.Invoke(Action)
I recommand using the IProgress interface.
Works like a charm for me and is pretty easy to use.
In your progressbarVM add
public double Actualprogress
{
get { return (double)GetValue(ActualprogressProperty); }
set { SetValue(ActualprogressProperty, value); }
}
public static readonly DependencyProperty ActualprogressProperty =
DependencyProperty.Register("Actualprogress", typeof(double), typeof(ProgressBar),
new PropertyMetadata(0.0));
then call your method as an asyn task using await like so :
var progress = new Progress<double>(progressPercent =>
progressBarVM.progressBar.Actualprogress = progressPercent);
Parser parser = new Parser();
ModelVMResult result = await Task.Run(() => parser.Parse(filename,progress));
then in your method "parse" just do:
float threshold = 0.0f;
for (int i = 0; i < count; i++)
{
if (i >= threshold)
{ progress.Report(prog += 1); threshold += count / 100.0f; }
this.Readline(reader, i);
}
Of course you need to bind your xaml progressbar.Value to ProgressbarVM.Actualprogress.
Then your progressbar will update and your app will still be responsive during the process.

DataContext - ListView - Refresh UI - INotifyPropertyChanged

Working on an windows store app, I try to update/refresh a listView when some data is updated. But despite all samples and documentations I read, it doesn't work...
Here my code behind :
(I do not provide my xaml files, it's just a sample listView).
So the class which implements the INotifyPropertyChanged interface:
public class Download : INotifyPropertyChanged
{
public enum DownloadState
{
Running,
Waiting,
Pausing,
Paused,
Cancelling,
Cancelled
};
private String Uri;
private StorageFile storageFile;
private String tempFileName;
private String fileName;
private String version ;
private long totalSize ;
private long downloadedBytes;
private DownloadState state;
private Protocol protocol;
public Download(String Uri, StorageFile file, String fileName, String version, long totalSize, Protocol protocol)
{
this.Uri = Uri;
this.storageFile = file;
this.tempFileName = "";
this.fileName = fileName;
this.version = version;
this.totalSize = totalSize;
this.downloadedBytes = 0;
this.state = DownloadState.Waiting;
this.protocol = protocol;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
System.Diagnostics.Debug.WriteLine("Update!"); //ok
if (PropertyChanged != null)
{
//PropertyChanged is always null and shouldn't.
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public DownloadState State
{
get{return this.state;}
set {
this.state = value;
NotifyPropertyChanged();
}
}
//+some others methods
}
}
And the main page of the metro app :
// The Basic Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234237
namespace ClientAirNavLight_WS
{
/// <summary>
/// A basic page that provides characteristics common to most applications.
/// </summary>
public sealed partial class MainPage : ClientAirNavLight_WS.Common.LayoutAwarePage
{
/// <summary>
/// Represent a Web Service proxy.
/// </summary>
private AirNavLight_WSClientClient proxyWS;
/// <summary>
/// Initiialize the component of the application's main page.
/// </summary>
public MainPage()
{
this.InitializeComponent();
}
/// <summary>
/// Populates the page with content passed during navigation. Any saved state is also
/// provided when recreating a page from a prior session.
/// </summary>
/// <param name="navigationParameter">The parameter value passed to
/// <see cref="Frame.Navigate(Type, Object)"/> when this page was initially requested.
/// </param>
/// <param name="pageState">A dictionary of state preserved by this page during an earlier
/// session. This will be null the first time a page is visited.</param>
protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
}
/// <summary>
/// Preserves state associated with this page in case the application is suspended or the
/// page is discarded from the navigation cache. Values must conform to the serialization
/// requirements of <see cref="SuspensionManager.SessionState"/>.
/// </summary>
/// <param name="pageState">An empty dictionary to be populated with serializable state.</param>
protected override void SaveState(Dictionary<String, Object> pageState)
{
}
//Simulate data update.
private async void Button_Click(object sender, RoutedEventArgs e)
{
object selected = this.ListResult.SelectedItem;
if (selected != null)
{
//simulate an update in data.
// ListView should be refresh in order to reflect changes made here.
Download dl = (Download)selected;
if (dl.State == Download.DownloadState.Paused)
{
dl.State = Download.DownloadState.Running;
}
else
{
dl.State = Download.DownloadState.Paused;
}
}
else
{
//Just add an item to the list view.
StorageFile file = await this.getStorageFile("test");
Download dl = new Download("192.128.2.14", file, "test", "1.2", 100, Protocol.HTTP);
this.ListResult.Items.Add(dl);
this.ListResult.DataContext = dl;// Does not work.
}
}
private async Task<StorageFile> getStorageFile(String suggestedFileName)
{
FileSavePicker savePicker = new FileSavePicker();
savePicker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
// Dropdown of file types the user can save the file as
savePicker.FileTypeChoices.Add("Application/pdf", new List<string>() { ".pdf" });
savePicker.FileTypeChoices.Add("Archive", new List<string>() { ".zip", ".rar", ".7z" });
savePicker.FileTypeChoices.Add("Plain-text", new List<string>() { ".txt" });
// Default file name if the user does not type one in or select a file to replace
savePicker.SuggestedFileName = suggestedFileName;
return await savePicker.PickSaveFileAsync();
}
}
}
So How am I supposed to use listView.DataContext? Am I misunderstanding how use INotifyPropertyChanged?
EDIT :
How my listView is define :
<ListView x:Name="ListResult" HorizontalAlignment="Left" Height="200" Margin="10,56,0,0" VerticalAlignment="Top" Width="653" SelectionMode="Single"/>
Why are you setting this.ListResult.DataContext = dl? When dealing with ListViews, the ItemsSource is the collection for the all the objects that get iterated, not the DataContext. Furthermore, ItemsSource should be a collection and not one item. So if you're binding to a property, that property should exist as an item's property in the ItemsSource collection.
However, it looks like you don't have a collection so you're adding the dl directly to the ListView with this.ListResult.Items.Add(dl). That approach should work, however take out the this.ListResult.DataContext = dl or set this.ListResult.ItemsSource to a collection and add dl to that collection
I'm not familiar with [CallerMemberName] but if you're having problems with propertyName being null, try the following
public DownloadState State
{
get{return this.state;}
set {
if( this.state != value )
{
this.state = value;
NotifyPropertyChanged("State");
}
}
}
Furthermore, all properties that you want to bind to should be public and call NotifyPropertyChanged, like the following
private long totalSize ;
public long TotalSize
{
get{return this.totalSize;}
set {
if( this.totalSize != value )
{
this.totalSize = value;
NotifyPropertyChanged("TotalSize");
}
}
}
In your Download class, the only Property that will update in the UI is State, since it has a getter and setter.
Without your XAML it's not possible to see what you are trying to display, but normally, you would bind properties of your DataContext object (dl in this case) to different controls in a datatemplate that the listview could then display.
But these properties need to be public and have getters and setters for INotifyPropertyChanged to update the UI that these properties are bound to.
For instance, if you wanted the filename to show up in the ListView, you would need, as a minimum, to declare the filename property like this:
public String fileName {get; set; }
Zangdak -- One way around that problem is each time the items change in your collection just set set the ItemSource of the ListView to null and then set it back to your collection. If you do this the UI will update and you will see your new items added to your collection.
Paige Ake

Categories

Resources