I have a MVVM WPF application in C#, NET 3.5 and Visual Studio 2008.
From the app main xaml I import a user control.
This user control has some public methods, there are two I am interested in.
One method to start an animation and another to stop it.
From my view's constructor in code-behind (xaml.cs), I call the user control public method to start the animation to show it to user while I am loading some data into my gridview within listview. The method to load the data is called form my view model.
So now, when the loading task is finished, I need to call the another user control public method to stop animation but I do not know how to do this from my view model.
Any ideas? I cannot touch the user control as this is not mine.
Below some piece of code.
XAML:
xmlns:controlProgress="clr-namespace:Common.XAML.Controls.Progress;assembly=Common.XAML"
<controlProgress:Progress x:Name="Progress"
Grid.ZIndex="3"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="150"
CustomText="Loading...">
Code-behind (xaml.cs):
public MyView(ViewModelSession vm)
: base(vm)
{
InitializeComponent();
Progress.StartAnimation();
}
View Model:
public MyViewModel(Session session)
: base(session)
{
this.LoadDataIntoGridView();
}
You can use the INotifyPropertyChanged Interface e.g. create an ViewModelBase
public class ViewModelBase
: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then you use this for your ViewModel and add a Property IsLoading
public class MyViewModel : ViewModelBase
{
private bool _isLoading;
public bool IsLoading
{
get { return _isLoading; }
set
{
if(_isLoading == value) return;
_isLoading = value;
OnPropertyChanged();
}
}
Then in your View Codebehind use the PropertyChanged event of the ViewModel to Start/Stop Animation.
Then you can set the bool in your ViewModel to start stop closing animation
in your view
UPDATE
public class MyView
{
private readonly MyViewModel _viewModel;
public MyView(MyViewModel viewModel)
: base(viewModel)
{
InitializeComponent();
_viewModel = viewModel;
_viewModel.PropertyChanged +=OnPropertyChanged;
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(MyViewModel.IsLoading))
{
if (_viewModel.IsLoading)
{
Progress.StartAnimation();
}
else
{
Progress.StopAnimation();
}
}
}
}
You could put a boolean property in your view model to track if the loading has been completed, after that the property will be set to true.
public class MyViewModel
{
public bool IsLoadComplete { get; set; }
public MyViewModel()
{
this.LoadDataIntoGridView();
}
}
Then in your codebehind you can start a Task to track changes in that property of the DataContext:
public MyView(MyViewModel vm)
{
InitializeComponent();
Progress.StartAnimation();
Task.Run(() =>
{
var dataContext = DataContext as MyViewModel;
while (true)
{
if (dataContext.IsLoadComplete)
break;
Task.Delay(100);
}
Dispatcher.BeginInvoke(new Action(() => { Progress.StopAnimation(); }));
});
}
You have to use Dispatcher.BeginInvoke to queue the call in the UI thread. Of course this is not a ready-to-production solution. You may provide Datacontext until View has been constructed in which case you must refactor, also you may keep track of the task you have just started and may be support cancellation with a CancellationToken. This is only a sample
Related
Here is my problem, I have the following data structure:
public class Job : INotifyPropertyChanged {
public StateEnum State {
get { return this.state; }
private set {
this.state = value;
this.OnPropertyChanged();
}
}
}
public class MainWindow : Window, INotifyPropertyChanged
public List<Job> Jobs {
get { return this.jobs; }
private set {
this.jobs = value;
this.OnPropertyChanged();
}
}
}
I want to display a global state summary of the jobs in the main window.
I first tried to make a data binding on the Jobs list, then use a custom IValueConverter to display the global state. Problem: It is not refreshed when the job states change (since it is bind to the collection and not the states).
ProgressState="{Binding Jobs, Converter={StaticResource JobsToProgressState}, ElementName=MainWindow}"
So I was trying to find a solution where I can bind all the nested properties of the jobs to a IMultiValueConverter. I did not find any syntax to make this work.
Is it possible to do something like that?
EDIT:
I want to do something like
ProgressState="{Binding Jobs[*].State, Converter={StaticResource JobsToProgressState}, ElementName=MainWindow}"
And retrieve an array containing all job states (StateEnum[]) in the JobsToProgressState converter.
The problem is that OnPropertyChanged is not fired when a record of an IList changes. You need to delegate the OnPropertyChanged of the Job up to your Jobs-List.
This rough implementation will do what you want.
public class Job : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
public StateEnum State {
get { return this.state; }
private set {
this.state = value;
this.OnPropertyChanged();
}
}
}
public class MainWindow : Window, INotifyPropertyChanged
public List<Job> Jobs {
get { return this.jobs; }
private set {
this.jobs = value;
foreach(var job in this.jobs)
{
job.PropertyChanged += job_PropertyChanged;
}
}
}
private void job_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
this.OnPropertyChanged("Jobs");
}
}
Don't forget to unwire your event registrations when you don't need them any more.
I am trying to get the text of a label to update on the front of end of my app.
At the moment Im using Message Centre to send a notification up to the view model and increment a number that should update on the label in the view.
Im using Xamarin Forms and PCL.
I can get the number to log out in the debug so I know the message centre is working. But its not updating the view.
the relevant Xaml:
<Label Text="{Binding counter}"
Grid.Row="0"/>
The code behind:
public partial class DriverDashboardView : ContentPage
{
private DriverDashboardViewModel driverdashboardviewmodel;
public DriverDashboardView()
{
InitializeComponent();
this.Title = "Driver's Dashboard";
BindingContext = driverdashboardviewmodel = new DriverDashboardViewModel();
dataList.ItemTapped += DataList_ItemTapped;
}
private void DataList_ItemTapped(object sender, ItemTappedEventArgs e)
{
DisplayAlert("Route Information","Various Data","OK");
}
protected async override void OnAppearing()
{
base.OnAppearing();
await driverdashboardviewmodel.GetLabelInfo();
}
}
The View Model:
public class DriverDashboardViewModel:BaseViewModel,INotifyPropertyChanged
{
private int messageCounter { get; set; }
public string counter { get { return messageCounter.ToString(); }
set {
if (Equals(value, messageCounter)) return;
messageCounter = Convert.ToInt32(value);
OnPropertyChanged(nameof(counter));
} }
public DriverDashboardViewModel()
{
MessagingCenter.Subscribe<App>((App)Application.Current, "Increase", (variable) => {
messageCounter++;
});
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
And the relevant section that implements the message centre:
Foregroundmessages.cs:
MessagingCenter.Send((App)Xamarin.Forms.Application.Current, "Increase");
As stated the messaging centre works fine. It gets as far as the view model but doesnt update the counter variable to the view. I have tried setting the counter as an int and a string hence the conversion in the get and set.
I also tried observable collection but that seemed redundant because its a single variable not a collection or list.
Any ideas?
your code is updating the private messageCounter property, not the public counter property that you are binding to. Updating messageCounter does not cause PropertyChanged to fire.
MessagingCenter.Subscribe<App>((App)Application.Current, "Increase", (variable) => {
messageCounter++;
});
I am writing a WPF application to learn the MVVM Design Pattern. I am fairly new to C# and WPF.
I am trying to pass some context when switching ViewModels, that then gets used in an ICommand implementation to call a method. But the ICommand won't update after receiving the context.
Basically I create an instance of an ICommand, which a button binds to and then (when passing the context) I create another instance that replaces it.
My question then: is there a way to rebind a command binding or is the state it had at the time of intialization unmodifiable.
What I'm trying to accomplish in code:
Command.cs
public class Command : ICommand
{
public Command(Action action) => this.action = action;
public event EventHandler CanExecuteChanged;
public virtual bool CanExecute(object parameter) => true;
public virtual void Execute(object parameter) => action();
Action action;
}
ObservableObject.cs
public abstract class ObservableObject : INotifyPropertyChanged
{
protected virtual void OnPropertyChanged(string propertyName) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
public event PropertyChangedEventHandler PropertyChanged;
}
FooModel.cs
public class FooModel
{
public int Number => 10;
}
BarModel.cs
public class BarModel
{
public int Number { get; set; }
}
BarViewModel.cs
public class BarViewModel : ObservableObject
{
public BarViewModel()
{
Bar = new BarModel();
BtnCommand = new Command(Reset);
}
public void Receive(object state)
{
if (state is FooModel foo)
{
Counter = Bar.Number = foo.Number;
// this won't reset the number to 10
BtnCommand = new Command(Reset);
// neither will this, why?
Reset();
}
}
public void Reset() => Counter = Bar.Number;
int counter;
public int Counter
{
get => counter;
set
{
counter = value;
OnNotifyPropertyChanged(nameof(Counter));
}
}
Command btnCommand;
public Command BtnCommand
{
get => btnCommand;
set
{
btnCommand = value;
OnNotifyPropertyChanged(nameof(BtnCommand));
}
}
BarModel Bar { get; private set; }
}
BarView.xaml
<UserControl
<! ... namespaces and such -->
>
<UserControl.DataContext>
<local:BarViewModel />
</UserControl.DataContext>
<Grid>
<Button Content="Click" Command="{Binding BtnCommand}"/>
</Grid>
</UserControl>
The Receive Method is invoked after creating the BarViewModel and it passes an instance of FooModel. When I set a breakpoint inside the if (state ...) block it says that the Bar.Number field is 10, but when it leaves the scope it's back to 0. I get a feeling that this is how it's supposed to work, but how can I accomplish the update of the Command context?
I tried to create a MCVE of my project, here's the link to dropbox. It's a VS 2017 Project using .NET 4.5.2
Screenshot of code in MCVE:
EDIT: changed fileupload URL
EDIT2: added screenshot
EDIT3: updated code
EDIT4: changed fileupload to dropbox
You are creating a new instance of the BarViewModel in your Bar view. Remove this XAML markup:
<UserControl.DataContext>
<local:BarViewModel />
</UserControl.DataContext>
Then the commands that you create in your Receive method should be invoked as expected.
I have a AppViewModel, that contains a menu on Top of the Window. On the AppViewModel construct, I'm showing a UserControl. In this UserControl I have a button, that calls another viewmodel (UserControl).
The idea is to keep the menu and working on the content of window. So, I have 1 window and 2 UserControls. This is correct?
How can I call another ViewModel from a button that is inside of a UserControl? Or, I have to call it from the Window? But the button it's inside of the UserControl!
My code:
class AppViewModel : Conductor<object>
{
private bool _MenuIsVisible;
public bool MenuIsVisible
{
get { return _MenuIsVisible; }
set
{
if (_MenuIsVisible != value)
{
_MenuIsVisible = value;
NotifyOfPropertyChange(() => MenuIsVisible);
}
}
}
public AppViewModel()
{
MenuIsVisible = true;
_ShowTutorial();
}
private void _ShowTutorial()
{
ActivateItem(new FirstViewModel());
}
}
public class FirstViewModel : Screen
{
protected override void OnActivate()
{
base.OnActivate();
}
}
On the FirstViewModel I have a button that needs to call SecondViewModel.
To navigate from the first ViewModel to the second ViewModel you could have a method in the first ViewModel like this:
public void NavigateToSecond()
{
var conductor = this.Parent as IConductor;
conductor.ActivateItem(new SecondViewModel());
}
The parent refers to the conductor which will take care of navigating for you.
Why does my textbox fail to update when I try to update it from another class?
I've instantiated the MainWindow class in my Email class, but when I try to do
main.trending.Text += emailText;
Am I doing something wrong?
You should bind your data.
Model
public class YourData : INotifyPropertyChanged
{
private string _textBoxData;
public YourData()
{
}
public string TextBoxData
{
get { return _textBoxData; }
set
{
_textBoxData = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("TextBoxData");
}
}
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
XAML Binding
Set data context in Codebehind
this.DataContext = YourData;
Bind Property
<TextBox Text="{Binding Path=Name2}"/>
See #sa_ddam213 comment. Dont do something like MainWindow main = new MainWindow(); inside Email class. Instead, pass the MainWindow object you already have.
Following codes will work:
public class MainWindow
{
public void MethodWhereYouCreateEmailClass()
{
Email email = new Email;
email.Main = this;
}
}
public class Email
{
public MainWindow main;
public void MethodWhereYouSetTrendingText()
{
main.trending.Text += emailText;
}
}
But I dont say that is best practice. I just try to keep it close to your existing code i guess.