question

EmonHaque-1485 avatar image
0 Votes"
EmonHaque-1485 asked EmonHaque-1485 edited

Is it possible to read and modify a DependencyProperty of Model/Viewmodel on a separate thread?

While trying to find a solution for this problem on browser, bing brought up this content on edge to tell me that there're 3 ways of databinding. Up until now I've always been using the second approach, INotifyPropertyChanged, in viewmodel to update UI and the only solution that works for my problem is reset the DataContext everytime I renew the Property and invoke PropertyChanged in viewmodel

After reading that article I've given it a try with this Model:

 class Model : DependencyObject
 {
     public static readonly DependencyProperty NameProperty, AddressProperty;
     static Model() {
         NameProperty = DependencyProperty.Register(nameof(Name), typeof(string), typeof(Model), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
         AddressProperty = DependencyProperty.Register(nameof(Address), typeof(string), typeof(Model), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
     }
     public string Name {
         get { return (string)GetValue(NameProperty); }
         set { SetValue(NameProperty, value); }
     }
     public string Address {
         get { return (string)GetValue(AddressProperty); }
         set { SetValue(AddressProperty, value); }
     }
 }

and this Viewmodel:

 class Viewmodel : DependencyObject
 {
     public static readonly DependencyProperty TheModelProperty;
     public ObservableCollection<Model> Collection { get; set; }
     public ICommand Add { get; set; }
     public ICommand Edit { get; set; }
     public Viewmodel() {
         TheModel = new Model();
         Collection = new ObservableCollection<Model>();
         BindingOperations.EnableCollectionSynchronization(Collection, Collection);
         Add = new Command(add, isValid);
         Edit = new Command(edit, (o) => true);
     }
     static Viewmodel() {
         TheModelProperty = DependencyProperty.Register(nameof(TheModel), typeof(Model), typeof(Viewmodel), new PropertyMetadata(null));
     }
     void add(object o) {
         Collection.Add(TheModel);
         TheModel = new Model();
     }
     bool isValid(object o) {
         return 
             !string.IsNullOrEmpty(TheModel.Name) &&
             !string.IsNullOrEmpty(TheModel.Address);
     }
     void edit(object o) {
         var model = o as Model;
         model.Name = "Edited";
         model.Address = "Edited";
     }
     public Model TheModel {
         get { return (Model)GetValue(TheModelProperty); }
         set { SetValue(TheModelProperty, value); }
     }
 }

for this UI in MainWindow.xaml:

 <Window.DataContext>
     <VM:Viewmodel/>
 </Window.DataContext>
 <Grid Margin="20" x:Name="grid">
     <Grid.RowDefinitions>
         <RowDefinition Height="Auto"/>
         <RowDefinition Height="Auto"/>
         <RowDefinition Height="Auto"/>
         <RowDefinition/>
         <RowDefinition Height="Auto"/>
     </Grid.RowDefinitions>
     <TextBox Text="{Binding TheModel.Name, UpdateSourceTrigger=PropertyChanged}"/>
     <TextBox Grid.Row="1" Text="{Binding TheModel.Address, UpdateSourceTrigger=PropertyChanged}"/>
     <Button Grid.Row="2" Content="Add" Command="{Binding Add}"/>
     <ListBox Grid.Row="3" Name="lb" ItemsSource="{Binding Collection}">
         <ListBox.ItemTemplate>
             <DataTemplate>
                 <StackPanel Orientation="Vertical">
                     <TextBlock Text="{Binding Name}"/>
                     <TextBlock Text="{Binding Address}"/>
                 </StackPanel>
             </DataTemplate>
         </ListBox.ItemTemplate>
     </ListBox>
     <Button Grid.Row="4" Content="Edit" Command="{Binding Edit}" CommandParameter="{Binding ElementName=lb, Path=SelectedItem}"/>
 </Grid>

and, interestingly, everything works as expected:

92900-test.gif

in my existing project, in some places, I access and modify model/viewmodel properties on different thread so I wanted to do the same with the following modified add and edit methods of my Viewmodel:

 void add(object o) {
     Task.Run(() => {
         Collection.Add(TheModel);
         TheModel = new Model();
     });
 }

 void edit(object o) {
     var model = o as Model;
     Task.Run(() => {
         model.Name = "Edited";
         model.Address = "Edited";
     });
 }

BUT it doesn't work! Is it possible to do that somehow?

windows-wpf
test.gif (121.6 KiB)
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.

1 Answer

PeterFleischer-3316 avatar image
1 Vote"
PeterFleischer-3316 answered EmonHaque-1485 edited

Hi,
the simplest way is to reroute assignment to UI thread like in following demo:

   class Viewmodel : DependencyObject
   {
     private readonly SynchronizationContext _synchronizationContext = SynchronizationContext.Current;
    
     public static readonly DependencyProperty TheModelProperty;
     public ObservableCollection<Model> Collection { get; set; }
     public ICommand Add { get; set; }
     public ICommand Edit { get; set; }
     public Viewmodel()
     {
       TheModel = new Model();
       Collection = new ObservableCollection<Model>();
       BindingOperations.EnableCollectionSynchronization(Collection, Collection);
       Add = new Command(add, isValid);
       Edit = new Command(edit, (o) => true);
     }
     static Viewmodel()
     {
       TheModelProperty = DependencyProperty.Register(nameof(TheModel), typeof(Model), typeof(Viewmodel), new PropertyMetadata(null));
     }
    
     //void add(object o)
     //{
     //  Collection.Add(TheModel);
     //  TheModel = new Model();
     //}
    
     void add(object o)
     {
       Task.Run(() =>
       {
         ExecuteOnSyncContext(() =>
         {
           Collection.Add(TheModel);
           TheModel = new Model();
         });
       });
     }
    
     bool isValid(object o)
     {
       return
           !string.IsNullOrEmpty(TheModel.Name) &&
           !string.IsNullOrEmpty(TheModel.Address);
     }
    
     //void edit(object o)
     //{
     //  var model = o as Model;
     //  model.Name = "Edited";
     //  model.Address = "Edited";
     //}
    
     void edit(object o)
     {
       var model = o as Model;
       Task.Run(() =>
       {
         ExecuteOnSyncContext(() =>
         {
           model.Name = "Edited";
           model.Address = "Edited";
         });
       });
     }
    
     public Model TheModel
     {
       get { return (Model)GetValue(TheModelProperty); }
       set { SetValue(TheModelProperty, value); }
     }
    
     private void ExecuteOnSyncContext(Action action)
     {
       if (SynchronizationContext.Current == _synchronizationContext) action();
       else _synchronizationContext.Send(_ => action(), null);
     }
   }
· 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.

anonymous user-3316, that will execute the task on the UI thread, right? Can I create TheModel on a different thread and perform operation on TheModel in that thread?

0 Votes 0 ·