question

scottthomson-6985 avatar image
0 Votes"
scottthomson-6985 asked scottthomson-6985 commented

C# WPF Problem with Viewmodels executing business logic twice. Once on Application Loading and then again when User Selects their View

I have a problem trying to find a way to avoid my ViewModels executing their business logic eg methods calling to database to load tables etc twice. I can see that when I run my application (windows desktop) the ViewModels execute their methods during the startup process of the application and then for those that a user then clicks on a button to bring their View up they execute again. I suspect my issue is that in the ViewModels I have calls in their constructors which then go to a method and this populate the lists by calling to database. IF I remove these methods from the constructors no business logic methods execute on load, however then I cant get the business logic to execute on the button click.

The above duplication of calls causes a long delay for user as one of the database calls takes quite a while eg 10 secs to bring in 7,000 records

I cant think of a way to remove these calls from the constructor but then get the ViewModel to execute these calls when their View is activated via the button on the parent View. Ive given MVVMLight messenger a go but had issues with the sequence. the message would not fire off at right sequence.

I will set out below what I think are the main pieces of code to try and give you an idea of how Ive wired things up. There must be an Industry norm for designing out this issue, but Im not a professional.

MainWindow View with several Child View/ViewModels:
<Window.Resources>
<DataTemplate DataType="{x:Type reports:ReportsTabViewModel}">
<reports:ReportsTabView />
</DataTemplate>
<DataTemplate DataType="{x:Type masterData:MasterDataTabViewModel}">
<masterData:MasterDataTabView />
</DataTemplate>
</Window.Resources>

MainWindow VIewModel is instantiated in the App.xaml.cs:
MainWindow app = new MainWindow();
MainWindowViewModel context = new MainWindowViewModel();
app.DataContext = context;
app.Show();

A series of buttons to bring into View the ViewModel/Views

A ContentControl where they bind when user clicks on their button:
<ContentControl x:Name="PageContentControl" Content="{Binding CurrentPageViewModel}" Grid.Row="1" Grid.RowSpan="2" Grid.Column="1"/>

Then the child View/Models (which are the CurrentPageViewModel):

Child View.xaml which is a UserControl:
<UserControl.DataContext>
<local:PeopleTabViewModel/>
</UserControl.DataContext>

Then the Child ViewModel constructor which fires twice (one call method shown that brings in data from database and is the step I need to avoid being done twice):

     public PeopleTabViewModel()
     {
         FillPeople();
         FillModules();
         SetPerson();
         SetCollectionViews(SelectedPerson.PersonID.ToString());
         ChangePerson();
     }

     private void FillPeople()
     {
         var q = (from a in db.People
                  where !a.Archived
                  orderby a.LastName
                  select a).ToList();
         _people = new List<Person>(q);
     }


dotnet-wpf-xaml
· 4
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

@scottthomson-6985
I can't accurately analyze the business logic twice issue according to the code you provided. What does CurrentPageViewModel refer to ? Does he have anything to do with PeopleTabViewModel? If it is possible, please show me enough code for me to make a sample to reproduce your issue.

0 Votes 0 ·

DaisyTan perhaps first i can ask you this question.

How do have a parameterless constructor in your ViewModel and then with a User clicking a button "enter" to the ViewModel and start executing its methods? At the moment the only way I know to start executing the ViewModels methods is to have a call in the constructor but this then mean it start onloading View/ViewModel and not when I want it to start

thanks
scott

0 Votes 0 ·

@scottthomson-6985
I used parameterless constructor in my ViewModel like answer1 and answer2. Both of them are very commune usage, did them give you some help? By the way, I am little confused with you last description in the last reply. What does mean it start onloading View/ViewModel and not when I want it to start mean?


0 Votes 0 ·

DaisyTan

Ok I will insert as much code as I can and try to explain myself. My problem is that with the design below of my Views and ViewModels and their code when my program starts up and before it shows on the screen the retrieval of my data from the database into the Lists is fired for each and every ViewModel, but only the first ViewModel in the collection is visible on screen. The gives user a delay and then when user clicks the button to select another ViewModel the code executes for the ViewModel again. I want to load my program , show the user the first ViewModel and execute the code in that first ViewModel and that it. Then when user clicks and chooses another ViewModel that ViewModel code then executes. So how to stop my code from executing all ViewModel methods on startup of program and only execute when they become visible.

0 Votes 0 ·
scottthomson-6985 avatar image
0 Votes"
scottthomson-6985 answered scottthomson-6985 commented

DaisyTan this editor will not allow me103240-viewmodelproblemscott.txt to insert many lines of code so all the code is in the attachment



· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

DaisyTan I can see now why I am instantiating all my view models on startup. its because in the MainWindow constructor I have the following code. So every time I create a new ViewModel it initialises them. So now I think I have to work out a way to only initialise them on the button click. If you have some best practice code for that it is appreicated to send to me else I should be able to think of some way

public MainWindowViewModel()
{
PageViewModels.Add(new DashboardTabViewModel());
PageViewModels.Add(new PeopleTabViewModel());
PageViewModels.Add(new CurriculumTabViewModel());
PageViewModels.Add(new AnalyseTabViewModel());
PageViewModels.Add(new PathwaysTabViewModel());
PageViewModels.Add(new ReportsTabViewModel());
PageViewModels.Add(new MasterDataTabViewModel());

         CurrentPageViewModel = PageViewModels[0];
         SetButtonRect(CurrentPageViewModel);

     }
0 Votes 0 ·
DaisyTian-1203 avatar image
0 Votes"
DaisyTian-1203 answered scottthomson-6985 commented

I made a demo to initialise data on the button click as below:
XAML code:

 <Window.DataContext>
         <local:ViewModel></local:ViewModel>
     </Window.DataContext>
     <StackPanel>
         <WrapPanel>
             <DataGrid Width="200" Height="200" DataContext="{Binding Model1s,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding }"></DataGrid>
             <Button Content="Load Data" Width="120" Height="38" Command="{Binding Btn1Cmd,Mode=TwoWay}" Margin="20 0 0 0"/>
         </WrapPanel>
    
         <WrapPanel Margin="0 30 0 0">
             <DataGrid Width="200" Height="200" ItemsSource="{Binding Model2s,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></DataGrid>
             <Button Content="Load Data" Width="120" Height="38" Command="{Binding Btn2Cmd}" Margin="20 0 0 0"/>
         </WrapPanel>
           
     </StackPanel>

C# code is:

   public class ViewModel: NotifyObject
     {
    
         public ObservableCollection<Model1> model1s;
         public ObservableCollection<Model1> Model1s
         {
             get { return model1s; }
             set
             {
                 this.model1s = value;
                 OnPropertyChanged("Model1s");
             }
         }
    
    
         public ObservableCollection<Model2> model2s;
         public ObservableCollection<Model2> Model2s
         {
             get { return model2s; }
             set
             {
                 this.model2s = value;
                 OnPropertyChanged("Model2s");
             }
         }
    
         public ViewModel()
         {
              
         }
    
         private RelayCommand _btn1Cmd;
         public RelayCommand Btn1Cmd
         {
             get
             {
                 if (_btn1Cmd == null) return new RelayCommand(() => LoadData1());
                 return _btn1Cmd;
             }
    
             set
             {
                 _btn1Cmd = value;
             }
         }
    
         private void LoadData1()
         {
             Model1s = new ObservableCollection<Model1>()
             {
                 new Model1(){Name="Jack",Sex="Gril"},
                 new Model1(){Name="Daniel",Sex="Boy"},
             };
         }
    
    
         private RelayCommand btn2Cmd;
         public RelayCommand Btn2Cmd
         {
             get
             {
                 if (btn2Cmd == null) return new RelayCommand(() => LoadData2());
                 return btn2Cmd;
             }
    
             set
             {
                 btn2Cmd = value;
             }
         }
    
         private void LoadData2()
         {
             Model2s = new ObservableCollection<Model2>()
             {
                 new Model2(){ID="001",Age=25},
                 new Model2(){ID="002",Age=20},
             };
         }
     }
    
     public class Model1
     {
         public string Name { get; set; }
         public string Sex { get; set; }
     }
    
     public class Model2
     {
         public string ID { get; set; }
         public int Age { get; set; }
     }
    
     public class NotifyObject : INotifyPropertyChanged
     {
         public event PropertyChangedEventHandler PropertyChanged;
    
         protected void OnPropertyChanged(string propertyName)
         {
             if (PropertyChanged != null)
             {
                 PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
             }
         }
    
     }

Result code is:
103803-3.gif


If the response is helpful, please click "Accept Answer" and upvote it.
Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


3.gif (32.6 KiB)
· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

DaisyTan OK I think I see the logic. Load the View but with its DataContext "empty" so no ViewModel logic is executed then on the button press populate the DataContext which will then enable business logic to fire in the ViewModels

I will redesign.

many thanks!
Scott

0 Votes 0 ·