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;
}
}
Related
I am developing something using wpf mvvm.
Currently, my implementation style is a bit unusual.
This is to prevent the sauce from gathering in one place.
Async void should be avoided, but I don't know how to avoid it.
Any solution?
Below is the source I used. (RelayCommand is generic and I need to create a new async command.)
<View.xaml>, <View.xaml.cs>
<BusyIndicator IsBusy="{IsBusy, mode=TwoWay}">
<Listbox ItemsSource="{Binding Models}"/>
<Button Command="{Binding OnClickButtonCommand}"
CommandParameter="whether the value(string) exists or not"/>
</BusyIndicator>
this.DataContext = new ViewModel();
<ViewModel.cs>
public ViewModel() { }
private isBusy;
public isBusy
{
get => isBusy;
set => SetField(ref isBusy, value);
}
public ObservableCollection<Model> Models { get; set; }
public ICommand OnClickButtonCommand { get => new ButtonCommand(this); }
<ButtonCommand.cs>
public ButtonCommand : RelayCommand
{
private ViewModel viewModel;
public ViewModel(ViewModel viewModel)
{
this.viewModel = viewModel
}
public override async void Execute(object obj)
{
viewModel.IsBusy = true;
await Task.Delay(1500);
viewModel.Models = new ObservableCollection<Model>(await Data());
viewModel.IsBusy = false;
}
private async Task<List<Model>> Data()
{
await Task.Delay(100);
var data = new List<Model>();
...
return data;
}
}
*Inserting await delay is to prevent debugging warning.
<CommandBase.cs>
public interface IRelayCommand : ICommand
{
new void Execute(object obj);
}
public abstract class RelayCommand : IRelayCommand
{
public RelayCommand() { }
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
bool ICommand.CanExecute(object obj)
{
if (obj?.ToString().Length == 0)
{
return false;
}
else
{
return true;
}
//return canExecute == null || this.canExecute.Invoke(parameter);
}
public abstract void Execute(object obj);
}
And if my code is weird or there is better code, please let me know!
Thanks. :)
https://johnthiriet.com/mvvm-going-async-with-async-command/
I've seen a lot of this site and other samples, but it's an example using a typical fuction,
so I can't apply it...
I'm using the AsyncCommand implementation from James Montemagno's MVVM-Helpers library (currently also implemented in the Xamarin Community Toolkit), you can take a look at the implementation here: AsyncCommand implementation
You can use it like this:
Install the Refactored.MvvmHelpers nuget package.
MyViewModel.cs
public class MyViewModel {
//other code goes here (Models collection, IsBusy, etc)
public ICommand DoWorkCommand => new AsyncCommand(DoWorkAsync);
public ICommand DoWorkWithParameterCommand => new AsyncCommand<bool>(DoWorkWithParameterAsync);
private async Task DoWorkAsync(){
//simulate work
await Task.Delay(1000);
}
private async Task DoWorkWithParameterAsync(bool itemExists){
//simulate work
await Task.Delay(1000);
}
}
MyView.cs
<BusyIndicator IsBusy="{IsBusy, mode=TwoWay}">
<Listbox ItemsSource="{Binding Models}"/>
<Button Command="{Binding DoWorkCommand}" />
<Button Command="{Binding DoWorkWithParameterCommand}" CommandParameter="False"/>
</BusyIndicator>
An example of my implementation of base classes:
BaseInpc,
RelayCommand, RelayCommandAsync,
RelayCommand<T>, RelayCommandAsync<T>.
There are a lot of verbose documentation tags in the code.
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Simplified
{
/// <summary>Base class with implementation of the <see cref="INotifyPropertyChanged"/> interface.</summary>
public abstract class BaseInpc : INotifyPropertyChanged
{
/// <inheritdoc cref="INotifyPropertyChanged"/>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>The protected method for raising the event <see cref = "PropertyChanged"/>.</summary>
/// <param name="propertyName">The name of the changed property.
/// If the value is not specified, the name of the method in which the call was made is used.</param>
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
/// <summary> Protected method for assigning a value to a field and raising
/// an event <see cref = "PropertyChanged" />. </summary>
/// <typeparam name = "T"> The type of the field and assigned value. </typeparam>
/// <param name = "propertyFiled"> Field reference. </param>
/// <param name = "newValue"> The value to assign. </param>
/// <param name = "propertyName"> The name of the changed property.
/// If no value is specified, then the name of the method
/// in which the call was made is used. </param>
/// <remarks> The method is intended for use in the property setter. <br/>
/// To check for changes,
/// used the <see cref = "object.Equals (object, object)" /> method.
/// If the assigned value is not equivalent to the field value,
/// then it is assigned to the field. <br/>
/// After the assignment, an event is created <see cref = "PropertyChanged" />
/// by calling the method <see cref = "RaisePropertyChanged (string)" />
/// passing the parameter <paramref name = "propertyName" />. <br/>
/// After the event is created,
/// the <see cref = "OnPropertyChanged (string, object, object)" />
/// method is called. </remarks>
protected void Set<T>(ref T propertyFiled, in T newValue, [CallerMemberName] in string propertyName = null)
{
if (!Equals(propertyFiled, newValue))
{
T oldValue = propertyFiled;
propertyFiled = newValue;
RaisePropertyChanged(propertyName);
OnPropertyChanged(propertyName, oldValue, newValue);
}
}
/// <summary> The protected virtual method is called after the property has been assigned a value and after the event is raised <see cref = "PropertyChanged" />. </summary>
/// <param name = "propertyName"> The name of the changed property. </param>
/// <param name = "oldValue"> The old value of the property. </param>
/// <param name = "newValue"> The new value of the property. </param>
/// <remarks> Can be overridden in derived classes to respond to property value changes. <br/>
/// It is recommended to call the base method as the first operator in the overridden method. <br/>
/// If the overridden method does not call the base class, then an unwanted change in the base class logic is possible. </remarks>
protected virtual void OnPropertyChanged(in string propertyName, in object oldValue, in object newValue) { }
}
}
using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
namespace Simplified
{
/// <summary> A class that implements <see cref = "ICommand" />. <br/>
/// Implementation taken from <see href="https://www.cyberforum.ru/wpf-silverlight/thread2390714-page4.html#post13535649"/>
/// and added a constructor for methods without a parameter.</summary>
public class RelayCommand : ICommand
{
protected readonly CanExecuteHandler<object> canExecute;
protected readonly ExecuteHandler<object> execute;
private readonly EventHandler requerySuggested;
/// <inheritdoc cref="ICommand.CanExecuteChanged"/>
public event EventHandler CanExecuteChanged;
/// <summary> Command constructor. </summary>
/// <param name = "execute"> Command method to execute. </param>
/// <param name = "canExecute"> Method that returns the state of the command. </param>
public RelayCommand(ExecuteHandler<object> execute, CanExecuteHandler<object> canExecute = null)
: this()
{
this.execute = execute ?? throw new ArgumentNullException(nameof(execute));
this.canExecute = canExecute;
}
/// <inheritdoc cref="RelayCommand(ExecuteHandler{object}, CanExecuteHandler{object})"/>
public RelayCommand(ExecuteHandler execute, CanExecuteHandler canExecute = null)
: this
(
p => execute(),
p => canExecute?.Invoke() ?? true
)
{ }
private readonly Dispatcher dispatcher = Application.Current.Dispatcher;
/// <summary> The method that raises the event <see cref="CanExecuteChanged"/>.</summary>
public void RaiseCanExecuteChanged()
{
if (dispatcher.CheckAccess())
{
invalidate();
}
else
{
_ = dispatcher.BeginInvoke(invalidate);
}
}
private readonly Action invalidate;
private RelayCommand()
{
invalidate = () => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
requerySuggested = (o, e) => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
CommandManager.RequerySuggested += requerySuggested;
}
/// <inheritdoc cref="ICommand.CanExecute(object)"/>
public bool CanExecute(object parameter)
{
return canExecute?.Invoke(parameter) ?? true;
}
/// <inheritdoc cref="ICommand.Execute(object)"/>
public void Execute(object parameter)
{
execute?.Invoke(parameter);
}
}
}
using System;
using System.Collections;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows.Input;
namespace Simplified
{
/// <summary>A class that implements asynchronous commands with the execution
/// of the <see cref="RelayCommand.Execute(object)"/> method in Task. </summary>
/// <remarks>Only one call to the <see cref="RelayCommand.Execute(object)"/>
/// method is allowed at a time.
/// During the execution of the method, the <see cref="IsBusy"/> flag is set and
/// the <see cref="RelayCommand.CanExecute(object)"/> method of the command will return false.
/// The <see cref="INotifyDataErrorInfo"/> interface is implemented to notify about
/// an erroneous call to the <see cref="RelayCommand.Execute(object)"/> method before
/// the previous execution completes and about exceptions during the
/// execution of <see cref="RelayCommand.Execute(object)"/>.
/// To notify about changes in property values, the <see cref="INotifyPropertyChanged"/>
/// interface is implemented.</remarks>
public class RelayCommandAsync : RelayCommand, ICommand, INotifyPropertyChanged, INotifyDataErrorInfo
{
/// <inheritdoc cref="INotifyPropertyChanged.PropertyChanged"/>
public event PropertyChangedEventHandler PropertyChanged;
/// <inheritdoc cref="INotifyDataErrorInfo.ErrorsChanged"/>
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
/// <summary>The command is in the execution state of the <see cref="RelayCommand.Execute(object)"/> method.</summary>
public bool IsBusy { get; private set; }
/// <inheritdoc cref="INotifyDataErrorInfo.HasErrors"/>
public bool HasErrors { get; private set; }
/// <summary>Exception from the last execution of the <see cref="RelayCommand.Execute(object)"/> method.</summary>
public Exception ExecuteException { get; private set; }
// A flag indicating a "call to execute busy a command" error.
private bool isBusyExecuteError;
/// <summary>Sets a value to the <see cref="IsBisy"/> property and notifies of its change.</summary>
/// <param name="isBusy">The value for the property.</param>
protected void SetIsBusy(bool isBusy)
{
if (IsBusy != isBusy)
{
IsBusy = isBusy;
PropertyChanged?.Invoke(this, Args.IsBusyPropertyEventArgs);
RaiseCanExecuteChanged();
}
}
/// <summary>Sets the HasErrors property and reports an entity-level error.</summary>
/// <param name="hasErrors">The value for the property.</param>
protected void SetEntityHasErrors(bool hasErrors)
=> SetHasErrors(hasErrors, Args.EntityLevelErrorsEventArgs);
/// <summary>Sets the HasErrors property and reports an entity or property level error.</summary>
/// <param name="hasErrors">The value for the property.</param>
/// <param name="args">Argument with data about the error level.</param>
protected void SetHasErrors(bool hasErrors, DataErrorsChangedEventArgs args)
{
if (HasErrors != hasErrors)
{
HasErrors = hasErrors;
PropertyChanged?.Invoke(this, Args.HasErrorsPropertyEventArgs);
}
ErrorsChanged?.Invoke(this, args);
}
/// <summary>Sets a value to the <see cref="ExecuteException"/> property and notifies of its change.</summary>
/// <param name="exception">The value for the property.</param>
protected void SetExecuteException(Exception exception)
{
if (ExecuteException != exception)
{
ExecuteException = exception;
PropertyChanged?.Invoke(this, Args.ExecuteExceptionPropertyEventArgs);
}
}
/// <inheritdoc cref="RelayCommand(ExecuteHandler{object}, CanExecuteHandler{object})"/>
public RelayCommandAsync(ExecuteHandler<object> execute, CanExecuteHandler<object> canExecute = null)
: this(new AsyncData(execute, canExecute))
{ }
/// <inheritdoc cref="RelayCommand(ExecuteHandler, CanExecuteHandler)"/>
public RelayCommandAsync(ExecuteHandler execute, CanExecuteHandler canExecute = null)
: this(new AsyncData(execute, canExecute))
{ }
// The field for storing additional, auxiliary data generated
// during the generation of the asynchronous method, wrapping
// the one obtained in the constructor.
private readonly AsyncData data;
/// <inheritdoc cref="RelayCommand(ExecuteHandler{object}, CanExecuteHandler{object})"/>
protected RelayCommandAsync(AsyncData data)
: base(data.ExecuteAsync, data.CanExecuteAsync)
{
this.data = data;
this.data.commandAsync = this;
}
/// <inheritdoc cref="INotifyDataErrorInfo.GetErrors(string)"/>
public IEnumerable GetErrors(string propertyName)
{
if (string.IsNullOrEmpty(propertyName))
{
if (isBusyExecuteError)
{
yield return Args.BusyExecuteErrorMessage;
}
if (ExecuteException != null)
{
yield return ExecuteException;
}
}
IEnumerable errors = GetErrorsOverride(propertyName);
if (errors != null)
{
foreach (var error in errors)
{
yield return error;
}
}
}
/// <summary>Method overridden in derived classes to add error information</summary>
/// <param name="propertyName">The name of the property to retrieve validation
/// errors for; or null or Empty, to retrieve entity-level errors.</param>
/// <returns>The validation errors for the property or entity.</returns>
protected virtual IEnumerable GetErrorsOverride(string propertyName)
=> null;
/// <summary>A class with persistent elements to avoid re-creating them frequently.</summary>
public static class Args
{
public const string BusyExecuteErrorMessage = "Called the execution of a command when it is busy.";
public static readonly PropertyChangedEventArgs IsBusyPropertyEventArgs = new PropertyChangedEventArgs(nameof(IsBusy));
public static readonly PropertyChangedEventArgs HasErrorsPropertyEventArgs = new PropertyChangedEventArgs(nameof(HasErrors));
public static readonly PropertyChangedEventArgs ExecuteExceptionPropertyEventArgs = new PropertyChangedEventArgs(nameof(ExecuteException));
public static readonly DataErrorsChangedEventArgs EntityLevelErrorsEventArgs = new DataErrorsChangedEventArgs(string.Empty);
}
/// <summary>A class for storing additional, auxiliary data and methods that are generated
/// when generating asynchronous methods that wrap the synchronous methods received
/// in the constructor.</summary>
protected class AsyncData
{
public RelayCommandAsync commandAsync;
public async void ExecuteAsync(object parameter)
{
if (commandAsync.IsBusy)
{
commandAsync.isBusyExecuteError = true;
commandAsync.SetEntityHasErrors(true);
}
else
{
commandAsync.SetIsBusy(true);
try
{
await Task.Run(() => execute(parameter));
commandAsync.isBusyExecuteError = false;
commandAsync.SetExecuteException(null);
commandAsync.SetEntityHasErrors(false);
}
catch (Exception ex)
{
commandAsync.SetExecuteException(ex);
commandAsync.SetEntityHasErrors(true);
}
finally
{
commandAsync.SetIsBusy(false);
}
}
}
public CanExecuteHandler<object> CanExecuteAsync { get; }
private bool canExecuteNullAsync(object parameter) => !commandAsync.IsBusy;
private bool canExecuteAsync(object parameter) => !commandAsync.IsBusy && canExecute(parameter);
private readonly ExecuteHandler<object> execute;
private readonly CanExecuteHandler<object> canExecute;
/// <inheritdoc cref="AsyncData(ExecuteHandler, CanExecuteHandler)"/>
public AsyncData(ExecuteHandler<object> execute, CanExecuteHandler<object> canExecute)
{
this.execute = execute ?? throw new ArgumentNullException(nameof(execute));
if (canExecute == null)
{
CanExecuteAsync = canExecuteNullAsync;
}
else
{
this.canExecute = canExecute;
CanExecuteAsync = canExecuteAsync;
}
}
/// <summary>Creates an instance.</summary>
/// <param name="execute">Synchronous Execute method.</param>
/// <param name="canExecute">Synchronous CanExecute method.</param>
public AsyncData(ExecuteHandler execute, CanExecuteHandler canExecute)
{
if (execute == null)
{
throw new ArgumentNullException(nameof(execute));
}
this.execute = p => execute();
if (canExecute == null)
{
CanExecuteAsync = canExecuteNullAsync;
}
else
{
this.canExecute = p => canExecute();
CanExecuteAsync = canExecuteAsync;
}
}
}
}
}
Some of the classes did not fit into the answer.
Take them from here.
An example of using RelayCommand for your code.
public class ViewModel : BaseInpc
{
private bool _isBusy;
public bool IsBusy
{
get => _isBusy;
set => Set(ref _isBusy, value);
}
public ObservableCollection<Model> Models { get; set; }
// Under no circumstances should you get an instance of a command in this way.
// As such, each time the property is accessed, a NEW instance of the command is created.
// As a result, in each element of the Window, in the ViewMode, etc. there are different instances everywhere.
// This is a very common cause of various bugs.
//
//public ICommand OnClickButtonCommand { get => new ButtonCommand(this); }
// One of the options for the correct team creation.
private RelayCommand _onClickButtonCommand;
public RelayCommand OnClickButtonCommand => _onClickButtonCommand
?? (_onClickButtonCommand = new RelayCommand(ExecuteOnClick));
// Executing command method
private async void ExecuteOnClick(object parameter)
{
IsBusy = true;
await Task.Delay(1500);
Models = new ObservableCollection<Model>(await Data());
IsBusy = false;
}
private async Task<List<Model>> Data()
{
await Task.Delay(100);
var data = new List<Model>();
// Some code
return data;
}
}
In such an implementation, you can create a method for each command, its own IsBusy property and other members of which it needs.
But to a large extent, all this (except for the synchronous executing method) can be encapsulated in the command itself.
Encapsulating the execution method does not make sense, since it is because of it that the command instances differ from each other.
My example using asynchronous commands
In the example, a blinking background, in order to understand at what moment the GUI lags occur - blinking during lags stops.
There are also several buttons to demonstrate the difference between synchronous and asynchronous method execution, and how errors and exceptions in them affect applications.
Click them in various combinations.
When errors and exceptions appear, they are displayed under the buttons at the bottom of the window.
If you have any questions during testing - ask.
using Simplified;
using System;
using System.Threading;
namespace RelayCommandAsyncTest
{
public class TestViewModel : BaseInpc
{
private int _count;
// Command execution counter (common for all commands)
public int Count { get => _count; set => Set(ref _count, value); }
// Аsynchronous command
public RelayCommandAsync TestCommandAsync { get; }
// Synchronous command
public RelayCommand TestCommand { get; }
// Synchronous command with Lags
public RelayCommand TestLagsCommand { get; }
// Synchronous command executing asynchronous command
// without checking it IsBusy.
public RelayCommand TestExecuteCommandAsync { get; }
// Long running execution method for validating
// an asynchronous command
private void ExecuteForAsync(object obj)
{
Thread.Sleep(2000);
Count++;
// If the command parameter is not passed,
// then there will be an exception
Thread.Sleep(Convert.ToInt32(obj));
}
// Long running execution method for creating
// lags in a synchronous command
private void ExecuteLags(object obj)
{
isExecuteLagsCommand = true;
TestLagsCommand.RaiseCanExecuteChanged();
Thread.Sleep(2000);
Count++;
// If the command parameter is not passed,
// then there will be an exception
Thread.Sleep(Convert.ToInt32(obj));
isExecuteLagsCommand = false;
TestLagsCommand.RaiseCanExecuteChanged();
}
bool isExecuteLagsCommand;
// Fast executing method for checking lags with
// a synchronous command
private void Execute(object obj)
{
Count++;
// If the command parameter is not passed,
// then there will be an exception
Thread.Sleep(Convert.ToInt32(obj));
}
// A command that executes an asynchronous command without checking if it is busy.
// If the asynchronous command is busy, it will generate a validation error.
private void ExecuteCommandAsync(object obj)
{
Count++;
TestCommandAsync.Execute(obj);
}
public TestViewModel()
{
TestCommandAsync = new RelayCommandAsync(ExecuteForAsync);
TestCommand = new RelayCommand(Execute);
TestLagsCommand = new RelayCommand(ExecuteLags, p => !isExecuteLagsCommand);
TestExecuteCommandAsync = new RelayCommand(ExecuteCommandAsync);
timer = new Timer
(
_ => TestCommandAsync.RaiseCanExecuteChanged(),
null,
0,
5000
);
}
// Timer to check for raising CanExecuteChanged from any thread.
private readonly Timer timer;
}
}
<Window x:Class="RelayCommandAsyncTest.CommandTestWindow"
x:Name="main"
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:local="clr-namespace:RelayCommandAsyncTest" xmlns:common="clr-namespace:Common;assembly=Common"
mc:Ignorable="d"
Title="CommandTestWindow" Height="1000" Width="800"
FontSize="30">
<FrameworkElement.Triggers>
<EventTrigger RoutedEvent="Loaded" SourceName="main">
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetName="FlashingBrush"
Storyboard.TargetProperty="Color"
Duration="0:0:0.5"
To="LightYellow"
AutoReverse="True"
RepeatBehavior="Forever"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</FrameworkElement.Triggers>
<FrameworkElement.DataContext>
<local:TestViewModel/>
</FrameworkElement.DataContext>
<FrameworkElement.Resources>
<ControlTemplate x:Key="validationTemplate">
<Border BorderThickness="5"
BorderBrush="Red">
<Grid IsHitTestVisible="False">
<Viewbox>
<TextBlock Foreground="Red" FontSize="80"
Text="! ! !"/>
</Viewbox>
<AdornedElementPlaceholder/>
</Grid>
</Border>
</ControlTemplate>
<Style TargetType="Button">
<Setter Property="Validation.ErrorTemplate" Value="{DynamicResource validationTemplate}"/>
</Style>
</FrameworkElement.Resources>
<Grid>
<Grid.Background>
<SolidColorBrush x:Name="FlashingBrush" Color="LightGreen"/>
</Grid.Background>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto" />
<RowDefinition/>
</Grid.RowDefinitions>
<UniformGrid Columns="2">
<Button x:Name="button" Content="Async throw Exception"
Command="{Binding TestCommandAsync}"
CommandParameter="qwerty" Margin="15"/>
<Button Content="Async Normal"
Command="{Binding TestCommandAsync}"
CommandParameter="2000" Margin="15"/>
<Button Content="Sync Lags"
Command="{Binding TestLagsCommand}"
CommandParameter="2000" Margin="15"/>
<Button Content="IsBusy is not checked"
Command="{Binding TestExecuteCommandAsync}" Margin="15"
CommandParameter="0">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding TestCommandAsync.HasErrors}"
Value="True">
<Setter Property="Foreground" Value="Red"/>
<Setter Property="FontWeight" Value="ExtraBold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<Button Content="Sync throw Exception"
Command="{Binding TestCommand}"
CommandParameter="qwerty" Margin="15"/>
<Button Content="Sync Normal"
Command="{Binding TestCommand}" Margin="15"
CommandParameter="2000"/>
</UniformGrid>
<TextBlock Grid.Row="1" Margin="5">
<Run Text="IsBusy:"/>
<Run Text="{Binding TestCommandAsync.IsBusy, Mode=OneWay}"/>
<Run Text=" "/>
<Run Text="HasErrors:"/>
<Run Text="{Binding TestCommandAsync.HasErrors, Mode=OneWay}"/>
<Run Text=" "/>
<Run Text="Count:"/>
<Run Text="{Binding Count}"/>
</TextBlock>
<ScrollViewer Grid.Row="2"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding ElementName=button, Path=(Validation.Errors)}"
DisplayMemberPath="ErrorContent"
FontSize="15"/>
</ScrollViewer>
</Grid>
</Window>
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.
I've been trying to integrate Caliburn.Micro MVVM framework in a C# WPF project I am in the middle off.
I currently only have three view models:
ShellViewModel - (A Window view with a ContentControl)
AboutViewModel - (A usercontrol view)
ChatViewModel - (Another usercontrol view)
Currently I am trying to use a button at the AboutView that is bind to the 'Chat()' method at the AboutViewModel and should take the user to the ChatView, yet I am testing this with the AboutViewModel. (As seen in the handler)
What I need is that all the Screen/ViewModels to be Singleton and only have one instance and when I try to change page, it returns to an already existant page.
The issue here is that I only have one instance registered when I do IoC.GetAllInstances(), the ShellViewModel and even though I've tried multiple configurations at the bootstrapper, I cannot register my other ViewModels in a way to make their instances "reachable"
I thank you for your time, and here is the code I think it's relevant for the issue:
Here is my bootstrapper:
public class AppBootstrapper : BootstrapperBase
{
private SimpleContainer _container = new SimpleContainer();
public AppBootstrapper()
{
Initialize();
var config = new TypeMappingConfiguration
{
DefaultSubNamespaceForViewModels = "ViewModel",
DefaultSubNamespaceForViews = "View"
};
ViewLocator.ConfigureTypeMappings(config);
Caliburn.Micro.ViewModelLocator.ConfigureTypeMappings(config);
}
protected override void Configure()
{
_container.Singleton<ShellViewModel, ShellViewModel>();
_container.Singleton<IWindowManager, WindowManager>();
//tried registering AboutViewModel in multiple ways
_container.Singleton<AboutViewModel, AboutViewModel>();
_container.RegisterSingleton(typeof(AboutViewModel), null,typeof(AboutViewModel));
/
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
DisplayRootViewFor<ShellViewModel>();
}
protected override object GetInstance(Type service, string key)
{
var instance = _container.GetInstance(service, key);
if (instance != null)
return instance;
throw new InvalidOperationException("Could not locate any instances.");
}
protected override IEnumerable<object> GetAllInstances(Type service)
{
return _container.GetAllInstances(service);
}
protected override void BuildUp(object instance)
{
_container.BuildUp(instance);
}
}
ShellViewModel.cs:
public class ShellViewModel : Conductor<object>, IHandle<NavigationMessage>
{
/// <summary>
/// Caliburn.Micro event aggregator. (Publish/Subscribe pattern)
/// </summary>
public IEventAggregator events = new EventAggregator();
public ShellViewModel()
{
//var aaa = IoC.Get<IEventAggregator>();
events.Subscribe(this);
ActivateItem(new AboutViewModel(events));
}
public void Handle(NavigationMessage message)
{
//var instance = IoC.GetInstance(message.ViewModelType,null);
var instances = IoC.GetAllInstances(null);
foreach(var i in instances)
{
MessageBox.Show(i.ToString());
}
ActivateItem(new AboutViewModel(events));
}
}
And the AboutViewModel.cs:
/// <summary>
/// ViewModel belonging to the AboutView.xaml.
/// </summary>
/// <seealso cref="AboutView.xaml"/>
public class AboutViewModel : Screen, IHandle<NavigationMessage>
{
private readonly IEventAggregator _eventAggregator;
/// <summary>
/// Private container for the 'Version' public property.
/// </summary>
/// <see cref="Version"/>
private string _version;
/// <summary>
/// Property containing a string of the application's current version (e.g.: 0.1.3.45)
/// </summary>
/// <see cref="_version"/>
[JsonIgnore]
public string Version
{
get
{
return _version;
}
set
{
_version = value;
NotifyOfPropertyChange(() => Version);
}
}
/// <summary>
/// Base constructor for the AboutViewModel class.
/// </summary>
public AboutViewModel(IEventAggregator eventAggregator)
{
Logging.Info("Initialize AboutViewModel", this.GetType());
Logging.Debug("Subscribing to the eventAggregator", this.GetType());
_eventAggregator = eventAggregator;
_eventAggregator.Subscribe(this);
_version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
Logging.Debug("Version loaded (" + _version + ")", this.GetType());
}
/// <summary>
/// Boolean method connected to the ChatCommand activates or deactivates based on it's return
/// </summary>
/// <param name="obj">Object of type GotoPageMessage received from the messenger</param>
public bool CanChat(object obj)
{
return true;
}
/// <summary>
/// Method connected to the ChatCommand that sends the user to the 'Chat' view
/// </summary>
/// <param name="obj">Object of type GotoPageMessage received from the messenger</param>
public void Chat(object obj)
{
_eventAggregator.PublishOnUIThread(new NavigationMessage(typeof(AboutViewModel)));
}
public void Handle(NavigationMessage message)
{
//This handle is used only to know how many instances I have active
MessageBox.Show("about");
}
}
Edit 1:
P.S.: I used to have my ShellViewModel as Conductor.Collection.OneActive. Still didn't work. Maybe AllActive may work?...
override the SelectAssemblies method for caliburn micro to locate all the views:
protected override IEnumerable<Assembly> SelectAssemblies()
{
return new[]
{
Assembly.GetExecutingAssembly(), typeof(MainViewModel).Assembly
};
}
more on the bootstrapper here.
I actually found a solution right now, without messing with Assemblies.
I noticed that the ShellViewModel's instance was accesible, and by saving the instance into a object and debugging it, I noticed all the viewModel instances I created were there at the 'Items'.
Basically this was what I did, in the handler that is inside the ShellViewModel:
public void Handle(NavigationMessage message)
{
ShellViewModel Shell = (ShellViewModel)IoC.GetInstance(typeof(ShellViewModel), null);
object Instance = null;
foreach (var item in Shell.Items)
{
if (item.ToString().Contains(message.ViewModelType.ToString()))
Instance = item;
}
object AuxObject = new object();
if (Instance == null)
{
try
{
Instance = Activator.CreateInstance(message.ViewModelType, Shell.events);
}
catch (Exception e)
{
MessageBox.Show(e.ToString());
}
}
ActivateItem(Instance);
}
Is there a way to change the language of all the controls in the form in runtime?
For example, I have a button in the form and it has a text of "Hello". How can I change that into different language on runtime? dynamically every language that I can set. Is there a way to do it??
I've found an answer, but it seems that its not working it has something to do with cultureinfo. Any suggestions how to do it?
This is my code
public partial class Form1 : BaseLanguageForm
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
this.TriggerLanguageChange("fr-FR");
}
}
public class LanguageArgs : EventArgs
{
string _languageSymbol;
/// <summary>
/// Gets the language symble.
/// </summary>
public string LanguageSymbol
{
get { return _languageSymbol; }
}
/// <summary>
/// Initializes a new instance of the <see cref="LanguageArgs"/> class.
/// </summary>
/// <param name="symbol">The symbol.</param>
public LanguageArgs(string symbol)
{
this._languageSymbol = symbol;
}
}
public class BaseLanguageForm : Form
{
/// <summary>
/// Triggers the language change event.
/// </summary>
/// <param name="languageSymbol">The language symbol.</param>
protected void TriggerLanguageChange(string languageSymbol)
{
if (Form1.onLanguageChanged != null)
{
LanguageArgs args = new LanguageArgs(languageSymbol);
Form1.onLanguageChanged(this, args);
}
}
/// <summary>
/// This should be triggered whenever the combo box value chages
/// It is protected, just incase you want to do any thing else specific to form instacne type
/// </summary>
protected static event EventHandler<LanguageArgs> onLanguageChanged;
/// <summary>
/// This will be called from your form's constuctor
/// (you don't need to do anything, the base class constuctor is called automatically)
/// </summary>
public BaseLanguageForm()
{
//registering to the event
BaseLanguageForm.onLanguageChanged += new EventHandler<LanguageArgs>(BaseLanguageForm_onLanguageChanged);
}
/// <summary>
/// The function that was regidtered to the event
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The e.</param>
void BaseLanguageForm_onLanguageChanged(object sender, LanguageArgs e)
{
string lang = e.LanguageSymbol;
foreach (Control c in this.Controls)
{
ComponentResourceManager crm = new ComponentResourceManager(typeof(Form1));
crm.ApplyResources(c, c.Name, new CultureInfo(lang));
}
}
}
There are two kinds of things you must translate: The controls that are visible on the form and the strings you you need during runtime to say, change a button's Text or a MessageBox's caption etc..
You translate the controls you have on the form in the designer:
First do all the layout stuff in the Laguage = Standard.
Then change the Languge in the properties tab to another language.
Now translate all Texts in all controls.
You also may need to change the layout a little to allow for longer texts; this is fine!
Look at the project explorer: For each form and for each language there is now a new FormN.lang.resx file, e.g:
Form1.resx
Form1.en.resx
Form1.de.resx
Now when you call a changeLanguage function, maybe like this one:
private void ChangeLanguage(Control ctl, string lang)
{
resources.ApplyResources(ctl, ctl.Name, new CultureInfo(lang));
foreach (Control c in ctl.Controls) ChangeLanguage(c, lang);
}
maybe like this:
private void cb_language_Click(object sender, EventArgs e)
{
if (cb_language.Text == "DE")
{
Thread.CurrentThread.CurrentUICulture = new CultureInfo("de-DE");
resources = new ComponentResourceManager(typeof(Form1));
ChangeLanguage(this, "de-DE");
cb_language.Text = "EN";
}
else
{
Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US");
resources = new ComponentResourceManager(typeof(Form1));
ChangeLanguage(this, "en-US");
cb_language.Text = "DE";
}
}
..all visible controls will change their Texts.
The second thing to do is to create strings resource files, best one for standard and one for each language (repeating the standard values!): Add new element - resource file with names like these:
strings.resx
strings.de.resx
strings.en.resx
Then you add one name-value pair for each string you need in code, e.g:
msgCap_OW File Exists
msgTxt_OW Overwrite (Yes)
Use next free Index? (No)
Cancel?
cb_autoScrapeStart Start Timed Screenshots
Note: By shift-enter in a value field you can enter multiline values..
And finally you must change your code to use the strings resources, e.g. like this:
MessageBox.Show(strings.msgTxt_OW, strings.msgCap_OW, ..);
or:
cb_autoScrape.Text = (scrapeTimer.Enabled ?
strings.cb_autoScrapeStop : strings.cb_autoScrapeStart);
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