0

My app is a translation app. It contains a translation list that is passed to different viewmodel. Those viewmodels migth modify those lists including add and remove operations. For this purpose, I convert this list to an ObservableCollection in the constructor and my list is no longer modified. I know converting to an ObservableCollection creates a new object and the references are no longer the same. It is working perfectly for the concerned view, but once I want to change to another view, the list isn't updated. I was wondering what was the best way to solve this problem?

I thought I could create a custom ObservableCollection that would contain the corresponding list and automatically update it when an add or remove operation would be done. Something that'd look similar to this.

View

public partial class MainWindow : Window
{
    private void ListViewItem_PreviewMouseDown(objectsender,MouseButtonEventArgs e)
    {
        // this is where I instanciate the viewModel, and the 
        // list<Translation> isn't modify once I close the view
        DataContext = new ModifyWordVM(translations);
    }
}

ViewModel

public class ModifyWordVM: INotifyPropertyChanged
{
    private ObservableCollection<TranslationVM> translations;
    public ObservableCollection<TranslationVM> Translations
    {
        get { return translations; }
        set { translations = value; OnPropertyChanged("Translations"); }
    }
    public ModifyWordVM(List<Translation> translations)
    {
        // Converting list to ObservableCollection
        Translations = ConvertionHelper.ConvertTo(translations);
    }
}

I'd like to know what is the cleaner way to get the modified list back.

CSDev
  • 3,177
  • 6
  • 19
  • 37
Grégory
  • 25
  • 4

1 Answers1

1

You should encapsulate the traslations and their operations. To do this just introduce a class e.g. TranslationService which is shared between all relevant view models. To omit a smelly Singleton I added an instance of the service to the App.xaml resources.

The idea is that all modifications of the translation list take place in one location or type. The same type that is the binding source for the view. When adding a new translation the view should invoke a ICommand on the view model. This command will invoke the AddTranslation method on the TranslationService. Same for remove. Any changes to the translation collection will now reflect across the application.

If you also want to catch modifications of the actual translations (e.g. rename or edit) the TranslationService need to handle the PropertyChanged event of the ObservableCollection items as well.
When an items property changed the TranslationService must respond by raising the PropertyChanged event for the ObservableCollection property Translations. This would require the items to implement INotifyPropertyChanged too.

App.xaml
Shared TranslationService instance

<Application.Resources>
    <TranslationService x:Key="TranslationService">
        <TranslationService.DatabaseService>
            <DatabaseService />
        </TranslationService.DatabaseService>
    </TranslationService>
</Application.Resources>

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    private void ListViewItem_PreviewMouseDown(objectsender,MouseButtonEventArgs e)
    {
        // Instantiate the view model and initialize DataContext from XAML instead.
        // This method became redundant.
    }
}

MainWindow.xaml

<Window.DataContext>
  <ModifyWordVM>
    <ModifyWordVM.TranslationService>

      <!-- Reference the shared instance -->
      <StaticResource ResourceKey="TranslationService" />
    </ModifyWordVM.TranslationService>
  </ModifyWordVM>
</Window.DataContext>

ModifyWordVM.cs

public class ModifyWordVM: INotifyPropertyChanged
{    
    public ModifyWordVM()
    {}

    public AddTranslation(Translation translation) => this.translationService.AddTranslation(translation);

    public RemoveTranslation(Translation translation) => this.translationService.RemoveTranslation(translation);

    public TranslationService TranslationService {get; set;}

    public ObservableCollection<TranslationVM> Translations => this.translationService.Translations; 

}

TranslationService.cs

public class TranslationService
{
    public TranslationService()
    {}

    public AddTranslation(Translation translation)
    {
        // Add translations
    }

    public RemoveTranslation(Translation translation)
    {
        // Remove translations
    }

    private DatabaseService databaseService;
    public DatabaseService DatabaseService
    {
        get => this.databaseService; 
        set 
        { 
            this.databaseService = value; 
            this.Translations = databaseService.getTranslations; 
         }
    }

    private ObservableCollection<TranslationVM> translations;
    public ObservableCollection<TranslationVM> Translations
    {
        get => this.translations;
        set 
        { 
            this.translations = value; 
            OnPropertyChanged("Translations"); 
         }
    }
}
BionicCode
  • 1
  • 4
  • 28
  • 44
  • Thank you, it works perfectly now. The only thing is that I didn't totally understand the what's going on in the App.xaml. Does it create an instance of my service ? – Grégory Jul 30 '19 at 22:32
  • Yes it creates a shared instance of the `TranslationService`. You can bind to this instance from everywhere in your code since it is global. Now every class can use the same `TranslaionService`, – BionicCode Jul 31 '19 at 08:17
  • Thank you for your conprehensive reply – Grégory Aug 01 '19 at 15:26