Execute Method from Codehind using ViewModel WPF - wpf

I've abandoned the MVVM midway through app development just to get this app out.
I've written a method in the code behind to update the database/datagrid etc.
My application navigation is using Commands to the ViewModel firing some event but never touches the code-behind except one time to initialize the class.
So basically I push the button one time and it works with the default initial setting but I can't call my code-behind Update() method anymore once the view as been intialized.
How can I call this code-behind method from the view model?
Thanks!!
Update code
//Navigation ViewModel
//PaneVm.cs
public CommandExtension NewAssignmentCommand { get; set; }
private void CreateCommands()
{
NewAssignmentCommand = new CommandExtension(NewAssignment, CanNewAssignment);
}
GlobalCommands.NewAssignmentCommand = NewAssignmentCommand;
private bool CanNewGroupAssignment(object obj)
{
return true;
}
private void NewGroupAssignment(object obj)
{
OnPropertyChanged("NewGroupAssignmentCommand");
}
//MainVM.cs
// [Events]
void _PaneVm_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "NewGroupAssignmentCommand")
WorkspaceVm.CurrentVm = new NewAssignmentsVm();
}
//NewAssignmentVm.cs
//Constructor
public NewAssignmentsVm()
{
var rc = new RepositoryContext();
_RoResearchers = new ObservableCollection<Researcher>(rc.ResearcherData.GetAllResearchers());
_QuarterDateTime = DateTime.Now;
CreateCommands();
}
//NewAssignment.cs
//Code-behind
//The method
private void UpdateGrid()
{
report_datagrid.ItemsSource = null;
using (var rc = new RepositoryContext())
{
if (quarter_datepicker.SelectedDate != null)
{
if (!String.IsNullOrEmpty(reportType))
researchers = rc.ResearcherData.GetResearchersWeeksByQuarter(Convert.ToDateTime(quarter_datepicker.SelectedDate), reportType).ToList();
}
}
}
UPDATE 2:
I solved my problem based off this answer. I created a Global Action
public static class GlobalCommands
{
public static Action UpdateGrid { get; set; }
}
Then in my code-behind constructor I set the value public
MyCodeBehind()
{
GlobalCommands.UpdateGrid = new Action(() => this.UpdateGrid());
}
Didn't need to bind to the context again. Everything else was the same. Thank you

Main idea is:
class MyCodeBehind
{
public MyCodeBehind()
{
Action action = new Action(()=> this.SomeMethodIWantToCall());
var myVM = new MyVM(action); // This is your ViewModel
this.DataContext = myVM;
}
private void SomeMethodIWantToCall(){...}
}
class MyVM
{
private Action action;
public MyVM(Action someAction)
{
this.action = someAction;
}
private void SomeMethodInVM()
{
this.action(); // Calls the method SomeMethodIWantToCall() in your code behind
}
}

Instead of letting code-behind know about viewmodel, You can make use of NotifyOnSourceUpdated in your xaml binding.
Something like this:
<TextBlock Grid.Row="1" Grid.Column="1" Name="RentText"
Text="{Binding Path=Rent, Mode=OneWay, NotifyOnTargetUpdated=True}"
TargetUpdated="OnTargetUpdated"/>
Here, 'OnTargetUpdated' is a handler in your code behind. This handler will be invoked when "Rent" property of ViewModel is changed.
Details at MSDN

Related

Datagrid remains empty after asynchronous initialization in view model constructor

I have a WPF application with a view containing a data grid and a view model with an observable collection that is initialized by calling an asynchronous method in the constructor. But the data grid remains empty upon running the code.
The view model class looks like this.
internal class MainWindowViewModel : INotifyPropertyChanged
{
private readonly IBookingRecordService service;
public event PropertyChangedEventHandler? PropertyChanged;
private ObservableCollection<BookingRecord> bookingRecords = new();
public ObservableCollection<BookingRecord> BookingRecords
{
get => bookingRecords;
set
{
bookingRecords = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(BookingRecords)));
}
}
public MainWindowViewModel()
{
service = new BookingRecordService();
Task.Run(() => LoadBookingRecords());
}
private async Task LoadBookingRecords()
{
BookingRecords = new ObservableCollection<BookingRecord>(await service.Get());
}
}
I all LoadBookingRecords() in the constructor, so that the data starts loading on initialization of the view model already but I do it asynchronously, so it does not block the UI thread and makes the application unresponsive.
I have tried waiting for the completion of the task in the constructor via
Task.Run(() => LoadBookingRecords()).Wait();
to check that this has something to do with the asynchronous function call. And indeed, if I wait for the method to finish in the constructor, the data grid displays correctly. But I don't want to wait for the task to finish on the UI thread because it blocks the UI.
I have read that you must raise the PropertyChanged event on the UI thread to trigger a UI update and I suppose that is the problem here. I have also read that one can use
Application.Current.Dispatcher.BeginInvoke()
to schedule a delegate to run on the UI thread as soon as possible, so I tried the following.
private async Task LoadBookingRecords()
{
await Application.Current.Dispatcher.BeginInvoke(new Action(async () =>
{
BookingRecords = new ObservableCollection<BookingRecord>(await service.Get());
}));
}
But this leaves the DataGrid empty as well.
"'asynchronous ... in constructor" is something you must avoid.
Async calls must be awaited, which can not be done in a constructor. Declare an awaitable Initialize method instead
public Task Initialize()
{
return LoadBookingRecords();
}
and call it in an async Loaded event handler in your MainWindow:
private static async void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
await viewModel.Initialize();
}
Alternatively, create a factory method like
public static async Task<MainWindowViewModel> Create()
{
var viewModel = new MainWindowViewModel();
await viewModel.LoadBookingRecords();
return viewModel;
}
and call that in the Loaded handler:
private static async void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
DataContext = await MainWindowViewModel.Create();
}
Building on Clemens' answer, I tried something a little different in order to avoid touching the MainWindow code-behind.
I removed the call on LoadBookingRecords in the constructor and instead created a delegate command as a property that holds this method.
internal class MainWindowViewModel : INotifyPropertyChanged
{
private readonly IBookingRecordService service;
private ObservableCollection<BookingRecord> bookingRecords = new();
public ICommand LoadBookingRecordsCommand { get; set; }
public ObservableCollection<BookingRecord> BookingRecords
{
get => bookingRecords;
set
{
bookingRecords = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(BookingRecords)));
}
}
public MainWindowViewModel()
{
service = new BookingRecordService();
LoadBookingRecordsCommand = new DelegateCommand(async _ => await LoadBookingRecords());
}
private async Task LoadBookingRecords()
{
BookingRecords = new ObservableCollection<BookingRecord>(await service.Get());
}
}
I then added the NuGet package Microsoft.Xaml.Behaviors.Wpf to the project and added the following namespace to the MainWindow XAML.
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
Finally, I bound the delegate command to the MainWindow's Loaded event.
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadBookingRecordsCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Now the data grid displays correctly after being loaded.

OnPropertyChanged wont change when used wth observable collection and single property

Loads the dataGrid and populates the Datagrid a row of 1'
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
update();
//this.DataContext = this;
}
CricketEvent events = new CricketEvent();
private void update()
{
events.updateList(new CricketEvent[1] { new CricketEvent(){Runs="1"} });
DG1.ItemsSource = events.RunsList;
}
private void DG1_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
{
Window1 windowToOpen = new Window1();
var selectedUser = this.DG1.SelectedItem;
windowToOpen.Show();
}
}
Main class that loads the OnPropertyChanged I have a List property and string property that calls the OnPropertyChanged but I want the individual "Runs" property to be updated on its own rather than the whole collection.
class CricketEvent : INotifyPropertyChanged
{
private ObservableCollection<CricketEvent> runsList;
public string runs { get; set; }
public CricketEvent(string numofRuns) {
this.Runs = numofRuns;
}
public CricketEvent() { }
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<CricketEvent> RunsList
{
get { return this.runsList; }
set
{
if (value != this.runsList)
{
this.runsList = value;
OnPropertyChanged("RunsList");
}
}
}
public string Runs
{
get { return runs; }
set
{
runs = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("Runs");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public ObservableCollection<CricketEvent> updateList(CricketEvent []events)
{
runsList = new ObservableCollection<CricketEvent>(events.ToList());
return runsList;
}
}
This is the update window that brings up a text box and should change the "1s" In the previous window to whatever is typed into the textbox
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
CricketEvent events = new CricketEvent();
MainWindow main = new MainWindow();
private void Button_Click(object sender, RoutedEventArgs e)
{
events.updateList(new CricketEvent[1] { new CricketEvent(txt1.Text.ToString()) });
main.DG1.ItemsSource = events.RunsList;
}
The Button_Click event in Window1 does not use the instance of MainWindow that is show - it creates a new Window instance (that is not shown) and adds the updated list to the DG1.ItemsSource property. To solve that, pass the original instance of Window to the created Window1 in constructor and use that.
However, you should review your update strategy (and code style) because there is potential for improvments:
It is not a good idea to create a new collection if you want to update just one property of one item. Observable collections provide change notification, so you dont have to recreate the collection at all.
Instead of assinging the collection in code behind, use databinding to bind the collection to the ItemsSource. DataBinding results in automatic update of GUI elements if the collection or one item of you collection changed.

WPF DependencyProperty chain notification

im exploring WPF world, i find a great example on the web about how to use binding on xml
http://www.codeproject.com/Articles/37854/How-to-Perform-WPF-Data-Binding-Using-LINQ-to-XML
Now im trying to extends this example: i want to create a "class in the middle" between the XElement and the UI and bind all togheder in a chain so, if i have a modification into the xml, then i have the property in the middle class updated then the UI updated too.
Here some code:
This is the class that wrap the XElement
public class XElementDataProvider : ObjectDataProvider
{
public XElementDataProvider()
{
ObjectInstance = XElement.Load(#"C:\MyFile.xml");
}
private static XElementDataProvider instance;
public static XElementDataProvider Instance
{
get
{
if (instance == null)
{
instance = new XElementDataProvider();
}
return instance;
}
}
}
This is the MiddleClass
public class MiddleClass : DependencyObject
{
XElementDataProvider xElementDataProvider;
XElement myxml;
public MiddleClass()
{
//here i get my dataprovider
xElementDataProvider = XElementDataProvider.Instance;
myxml = xElementDataProvider.Data as XElement;
//i bind my internal collection to the Elements...
Binding binding = new Binding("Elements[book]")
{
Source = myxml,
Mode = BindingMode.Default//here i cant use TwoWay, give me //back an exeption
};
BindingOperations.SetBinding(this, XBookListProperty, binding);
//just to have confirmation of the adding
myxml.Changed += new EventHandler<XObjectChangeEventArgs (myxml_Changed);
}
void myxml_Changed(object sender, XObjectChangeEventArgs e)
{
}
//i use a DependencyProperty to have also a change callback
public static readonly DependencyProperty XBookListProperty =
DependencyProperty.Register("XBookList", typeof(IEnumerable),
typeof(MiddleClass),
new PropertyMetadata(XBookPropertyChanged)
);
//here i have a notification only at start but no when i add a new book
private static void XBookPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MiddleClass middleClass = d as MiddleClass;
middleClass.XBookPropertyChanged((IEnumerable)e.OldValue, (IEnumerable)e.NewValue);
}
private void XBookPropertyChanged(IEnumerable old, IEnumerable newValue)
{
}
//this is the propery i finally want to expose to the UI but im not able //to keep updated
public List<Book> bookList;
public List<Book> BookList
{
get
{
return bookList;
}
set
{
bookList = value;
}
}
//this is my internal list binded to the xml
private IEnumerable XBookList
{
get
{
return (IEnumerable)GetValue(XBookListProperty);
}
set
{
SetValue(XBookListProperty, value);
}
}
//here i try to add a book addind direcly to the xml//i expect a //notification of propery changed...but nothing
public bool AddBook(string name)
{
XElement newWorkSheet = new XElement("Book",
new XAttribute("Name", name)
);
myxml.Add(newWorkSheet);
return true;
}
Book is a class thar repersents a book, let say it has only a name propery for now.
The UI class misses but it should bind on public List<Book> BookList and show books names to the user in a ListBox
Enyone knows why i dont recive any notification...or what i have to do to keep the public List<Book> BookList synchronized with private IEnumerable<XBookList>?
OK, after many attempts, the only solution I found is this one:
to have notifications when something changes in the IEnumerable<XBookList> you need to clear it ad rebind after you modify it.
In this way you have a first, not used notification, about the clear and then another notification about the new set.
Then in the handler you can synchronize the new list with the old one.
public bool AddBook(string name)
{
XElement newWorkSheet = new XElement("Book",
new XAttribute("Name", name)
);
myxml.Add(newWorkSheet);
ClearValue(XBookListProperty);
Binding binding = new Binding("Elements[book]")
{
Source = myxml,
Mode = BindingMode.Default
};
BindingOperations.SetBinding(this, XBookListProperty, binding);
return true;
}

How to Notify That View Should Get New Value of Calculated Field

I am working on a WP7 app that displays some times on one page. I have a code behind that has an ObservableCollection of objects. Each object has a calculated property that uses DateTime.Now to determine the time that's displayed on the page. I can't figure out how to "notify" that the property has changed since the property doesn't change, the current time is changing (just once per second). Any ideas? Here's the jist of what I've got:
//my business object
public class Widget
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
private DateTime? _start;
public DateTime? Start
{
get { return _start; }
set { _start = value; }
}
public TimeSpan? TimeSinceStart
{
get { return Start.HasValue ? DateTime.Now - Start.Value : default(TimeSpan); }
}
}
//my viewmodel
public class WidgetDisplayerViewModel : BaseViewModel
{
public WidgetDisplayerViewModel()
{
TimeUpdateTimer.Tick += new EventHandler(TimeUpdateTimer_Tick);
TimeUpdateTimer.Interval = new TimeSpan(0, 0, 1);
TimeUpdateTimer.Start();
}
public WidgetDisplayerViewModel(string selectedCategory) : this()
{
Category = MockDataService.GetCategory(selectedCategory);
Category.Widgets = MockDataService.GetWidgets(selectedCategory).ToObservableCollection();
}
public DispatcherTimer TimeUpdateTimer = new DispatcherTimer();
private DateTime _currentTime;
public DateTime CurrentTime
{
get { return _currentTime; }
set {
_currentTime = value;
NotifyPropertyChanged("CurrentTime");
}
}
public Category Category { get; set; }
void TimeUpdateTimer_Tick(object sender, EventArgs e)
{
CurrentTime = DateTime.Now;
}
}
And then the view is very simple and just needs to display the CurrentTime and then for each Widget in the collection it needs to show the TimeSinceStart. The CurrentTime is getting updated each second by the timer and that gets propogated to the view. That one is easy because the timer is setting it and so I have a chance to call NotifyPropertyChanged("CurrentTime"), but how would I "notify" that all of the TimeSinceStart getters should be called to update the calculated value for each Widget since I'm not setting them?
Thanks!
You'll have to manually refresh the property one way or another. I see you already have a timer ticking every second. So I can suggest you two solutions:
1/ Define a "UpdateTime" method in the Widget object. In this method, call NotifyPropertyChanged("TimeSinceStart"). When the timer is ticking, enumerate the list of widgets, and call the UpdateTime method on each.
2/ Create a global object implementing the INotifyPropertyChanged interface, and holding the value of CurrentTime. Make each of your Widget objects subscribe to the PropertyChanged event of this global class to know when the time is updated. Then, when the event is triggered, call NotifyPropertyChanged("TimeSinceStart").
This can be a tricky one to work out and it can get very messy very fast.
I would suggest you stick with your current approach of having only one timer which is initialised in the main viewmodel. You then have to ask yourself the question - does the age (TimeSinceStart) of the Widget belong on the Widget, or is it purely for display/informational purposes? Is it a core piece of information that each Widget must keep during its lifespan?
This looks to me like it is for display purposes only. So my suggestion is this: once you have called GetWidgets, you could enumerate through each Widget and wrap it in a thin viewmodel of its own. The constructor for that viewmodel takes two parameters - the timer from the main viewmodel, and the Widget. You then subscribe to the timer's Tick event, and from that you notify that the TimeSinceStart property has changed.
public class WidgetWrapper : INotifyPropertyChanged
{
public WidgetWrapper(DispatcherTimer timer, Widget widget)
{
_widget = widget;
timer.Tick += TimerTick;
}
private void TimerTick(object sender, EventArgs e)
{
OnPropertyChanged("TimeSinceStart");
}
public Widget Widget { get { return _widget; } }
public TimeSpan? TimeSinceStart
{
get { return _widget.Start.HasValue ? DateTime.Now - _widget.Start.Value : default(TimeSpan); }
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
private readonly Widget _widget;
}
public class WidgetDisplayerViewModel : BaseViewModel
{
public WidgetDisplayerViewModel(string selectedCategory) : this()
{
Category = MockDataService.GetCategory(selectedCategory);
var wrappedWidgets = new ObservableCollection<WidgetWrapper>();
MockDataService.GetWidgets(selectedCategory).ForEach(widget => wrappedWidgets.Add(new WidgetWrapper(TimeUpdateTimer, widget)));
Category.Widgets = wrappedWidgets;
}
}
Wrapping a DTO (entity, Data Transfer Object) with its own viewmodel is a quite common approach when adding functionality to an entity. If you use this appoach you will have to slightly modify any UI bindings that were targetting properties on the Widget, as those UI elements will now be dealing with a WidgetWrapper (or you can just surface the required properties in the WidgetWrapper itself, then no bindings have to change).
Invoke the NotifyPropertyChanged method for the specified property.
public DateTime CurrentTime
{
get { return _currentTime; }
set {
_currentTime = value;
NotifyPropertyChanged("CurrentTime");
NotifyPropertyChanged("TimeSinceStart");
}
}
Subscribe all widgets to CurrentTime PropertyChanged event in Widget constructor
private Widget()
{
App.ViewModel.PropertyChanged += (s, e) =>
{
if (e.PropertyName.Equals("CurrentTime")
{
NotifyPropertyChanged("TimeSinceStart");
}
};
}

Show BusyIndicator from WPF.ExtendedToolkit in Caliburn.Micro

Hi I try show busy indicator in shell which is wpf window.
In shell view I have this:
<Grid>
<extToolkit:BusyIndicator IsBusy="{Binding Path=ShellIsBusy, Mode=OneWay,
UpdateSourceTrigger=PropertyChanged}"
BusyContent="{Binding Path=BusyMessage,Mode=OneWay,
UpdateSourceTrigger=PropertyChanged}">
<ContentControl x:Name="ActiveItem" />
</extToolkit:BusyIndicator>
</Grid>
Shell model class is here:
[Export(typeof(IShellViewModel))]
public class ShellViewModel : Conductor<IScreen>.Collection.OneActive,
IShellViewModel, IPartImportsSatisfiedNotification
{
[Import]
internal IJinglePlayer JinglePlayer { get; set; }
private bool _isBusy;
private string _busyMessage;
public bool ShellIsBusy
{
get { return _isBusy; }
set
{
_isBusy = value;
NotifyOfPropertyChange(()=>ShellIsBusy);
}
}
public string BussyMessage
{
get { return _busyMessage; }
set
{
_busyMessage = value;
NotifyOfPropertyChange(()=>BussyMessage);
}
}
protected override void OnInitialize()
{
Show1();
base.OnInitialize();
JinglePlayer.PlayStartUp();
}
public void Show1()
{
var vm = IoC.Get<ILogOnViewModel>();
ActivateItem(vm);
}
public void Show2(IAccount account)
{
ActiveItem.Deactivate(true);
var vm = IoC.Get<IMeViewModel>();
vm.Account = account;
ActivateItem(vm); }
public void OnImportsSatisfied()
{
}
}
I run app, from active view model class I call this:
[Import]
internal IShellViewModel Shell { get; set; }
//...
Shell.ShellIsBusy = true;
Shell.BusyMessage = "logging";
//long task
Shell.Show2(logOnResult.ReturnValue);
Problem is that busy indicator is showed in the moment when is active another view.
I post my solution, maybe someone will have better idea. Problem is that long running task keep UI thread busy, so I call this task and shell method on active new view in another thread.
Something like this:
Task.Factory.StartNew(() => { //long task });
Task.Factory.StartNew(() => { Shell.Show2(...); });
This unblock UI thread and BusyIndicator can be displayed.

Resources