Pushing read-only GUI properties back into ViewModel

Cover Image for Pushing read-only GUI properties back into ViewModel
Matheus Mello
Matheus Mello
published a few days ago. updated a few hours ago

Pushing read-only GUI properties back into ViewModel ๐Ÿ“๐Ÿ”

So you want your ViewModel to always be aware of the current state of some read-only dependency properties from the View? ๐Ÿค” Not to worry, we've got you covered! In this guide, we'll address the common issues and provide you with easy solutions to keep your ViewModel in the loop. Let's dive in! ๐Ÿ’ชโœจ

The Problem ๐Ÿ˜•โ“

In your specific case, you have a GUI that includes a FlowDocumentPageViewer, which exposes two read-only dependency properties called CanGoToPreviousPage and CanGoToNextPage. Your goal is to ensure that your ViewModel always knows the values of these two properties. Sounds simple, right? Well, unfortunately, it's not as straightforward as it seems. ๐Ÿ˜ž

The approach you thought of initially was using a OneWayToSource data binding, like so:

<FlowDocumentPageViewer
    CanGoToNextPage="{Binding NextPageAvailable, Mode=OneWayToSource}" ...>

While this approach would be perfect if it worked, you quickly found out that it doesn't compile. An error message slapped you in the face, stating that the 'CanGoToPreviousPage' property is read-only and cannot be set from markup. Ouch! Turns out, read-only properties don't support any kind of data binding, even read-only data binding. ๐Ÿ™…โ€โ™€๏ธ

You also considered making your ViewModel's properties be DependencyProperties and creating a one-way binding going the other way. However, this approach violates the separation of concerns principle, as the ViewModel would need a reference to the View, which goes against the MVVM databinding pattern you're using. ๐Ÿ™…โ€โ™‚๏ธ

To make matters worse, the FlowDocumentPageViewer doesn't expose a CanGoToNextPageChanged event, making it difficult to get change notifications from a DependencyProperty without creating another one to bind it to. Talk about overkill! ๐Ÿ˜ฉ

The Solution(s) ๐Ÿ’กโœ…

Fear not, for there are a couple of solutions you can employ to overcome this challenge. Let's take a look at them:

1. Mediator Pattern ๐Ÿ›๏ธ

One possible solution is to implement the Mediator pattern. This pattern allows different objects to communicate with each other without having direct references, thus preserving the separation of concerns. In your case, you can create a mediator class that listens for changes in the FlowDocumentPageViewer's properties and notifies your ViewModel accordingly.

Here's a simplified example of how you can implement the Mediator pattern:

public class Mediator
{
    public static Mediator Instance { get; } = new Mediator();

    public event Action<bool> CanGoToNextPageChanged;

    private Mediator() { } // Singleton

    public void NotifyCanGoToNextPageChanged(bool value)
    {
        CanGoToNextPageChanged?.Invoke(value);
    }
}

In your View, you can then subscribe to the CanGoToNextPageChanged event and notify the mediator when the property changes:

<FlowDocumentPageViewer CanGoToNextPage="{Binding NextPageAvailable}"
                        Loaded="FlowDocumentPageViewer_Loaded" ...>
private void FlowDocumentPageViewer_Loaded(object sender, RoutedEventArgs e)
{
    var viewer = (FlowDocumentPageViewer)sender;

    viewer.CanGoToNextPageChanged += (s, canGoToNextPage) =>
    {
        Mediator.Instance.NotifyCanGoToNextPageChanged(canGoToNextPage);
    };
}

Finally, in your ViewModel, you can subscribe to the mediator's event and update your property accordingly:

public class ViewModel : INotifyPropertyChanged
{
    ...

    private bool _nextPageAvailable;
    public bool NextPageAvailable
    {
        get => _nextPageAvailable;
        set
        {
            if (_nextPageAvailable != value)
            {
                _nextPageAvailable = value;
                OnPropertyChanged();
            }
        }
    }

    public ViewModel()
    {
        Mediator.Instance.CanGoToNextPageChanged += (canGoToNextPage) =>
        {
            NextPageAvailable = canGoToNextPage;
        };
    }

    ...
}

And there you have it! With the Mediator pattern, your ViewModel will always stay informed about any changes in the FlowDocumentPageViewer's read-only property.

2. Attached Properties ๐Ÿ“Ž

Another approach you can take is using attached properties. Attached properties allow you to extend existing controls without modifying their code or creating new dependencies. By creating an attached property for the FlowDocumentPageViewer, you can bind it to your ViewModel and keep track of the read-only property changes.

Here's a simplified example of how you can implement this approach:

public static class FlowDocumentPageViewerExtensions
{
    public static readonly DependencyProperty NextPageAvailableProperty =
        DependencyProperty.RegisterAttached("NextPageAvailable", typeof(bool), typeof(FlowDocumentPageViewerExtensions),
            new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.None));

    public static bool GetNextPageAvailable(DependencyObject obj)
    {
        return (bool)obj.GetValue(NextPageAvailableProperty);
    }

    public static void SetNextPageAvailable(DependencyObject obj, bool value)
    {
        obj.SetValue(NextPageAvailableProperty, value);
    }
}

In your View, you can now use the attached property to bind it to your ViewModel like this:

<FlowDocumentPageViewer local:FlowDocumentPageViewerExtensions.NextPageAvailable="{Binding NextPageAvailable}" ...>

Then, in your ViewModel, you can update the NextPageAvailable property whenever the attached property changes:

public class ViewModel : INotifyPropertyChanged
{
    ...

    private bool _nextPageAvailable;
    public bool NextPageAvailable
    {
        get => _nextPageAvailable;
        set
        {
            if (_nextPageAvailable != value)
            {
                _nextPageAvailable = value;
                OnPropertyChanged();
            }
        }
    }

    ...
}

By using attached properties, you achieve the desired behavior without violating the separation of concerns.

Take Action! ๐Ÿ’ช๐Ÿ”ง

Now that you have a couple of solutions to tackle the challenge of pushing read-only GUI properties back into your ViewModel, it's time to take action! Choose the approach that best suits your needs and give it a try. And don't forget to let us know how it goes! We'd love to hear about your experience and any other tips or tricks you discover along the way. Happy coding! ๐Ÿ˜„๐Ÿ‘จโ€๐Ÿ’ป

P.S. Have you encountered similar challenges in your projects? How did you solve them? Share your thoughts, experiences, and ideas in the comments below! Let's learn from each other and grow together as a community! ๐Ÿ’ฌ๐Ÿš€


More Stories

Cover Image for How can I echo a newline in a batch file?

How can I echo a newline in a batch file?

updated a few hours ago
batch-filenewlinewindows

๐Ÿ”ฅ ๐Ÿ’ป ๐Ÿ†’ Title: "Getting a Fresh Start: How to Echo a Newline in a Batch File" Introduction: Hey there, tech enthusiasts! Have you ever found yourself in a sticky situation with your batch file output? We've got your back! In this exciting blog post, we

Matheus Mello
Matheus Mello
Cover Image for How do I run Redis on Windows?

How do I run Redis on Windows?

updated a few hours ago
rediswindows

# Running Redis on Windows: Easy Solutions for Redis Enthusiasts! ๐Ÿš€ Redis is a powerful and popular in-memory data structure store that offers blazing-fast performance and versatility. However, if you're a Windows user, you might have stumbled upon the c

Matheus Mello
Matheus Mello
Cover Image for Best way to strip punctuation from a string

Best way to strip punctuation from a string

updated a few hours ago
punctuationpythonstring

# The Art of Stripping Punctuation: Simplifying Your Strings ๐Ÿ’ฅโœ‚๏ธ Are you tired of dealing with pesky punctuation marks that cause chaos in your strings? Have no fear, for we have a solution that will strip those buggers away and leave your texts clean an

Matheus Mello
Matheus Mello
Cover Image for Purge or recreate a Ruby on Rails database

Purge or recreate a Ruby on Rails database

updated a few hours ago
rakeruby-on-railsruby-on-rails-3

# Purge or Recreate a Ruby on Rails Database: A Simple Guide ๐Ÿš€ So, you have a Ruby on Rails database that's full of data, and you're now considering deleting everything and starting from scratch. Should you purge the database or recreate it? ๐Ÿค” Well, my

Matheus Mello
Matheus Mello