0

I need to change the Visibility of a Button on a View from method call from within a class.

I have tried accessing the VeiwModel by exposing it in the class, and then had success in changing the Property "ShowRedHat" from true to false, but this does not update the Visibility of the Button in the View. This also double loads the ViewModel, which is not acceptable in my solution.

Any help is appreciated.

The class:

   public class HatEngine
   {
      
       public void SetShowRedHat()
       {
          ????.ShowRedHat = false;
       }
   }

The Property in the ViewModel:

public class MyViewModel : ObservableObject
{
    private bool _showRedHat;
    public bool ShowRedHat
    {
        get { return _showRedHat; }
        set
        {
            OnPropertyChanged(ref _showRedHat, value);
        }
    }
}

The Button in the View:

    <Button Content="Red Hat"                    
            Command="{Binding RedHatCommand}"
            Visibility="{Binding ShowRedHat, Converter={StaticResource BoolToVis}}"/>
Slip
  • 23
  • 6
  • Why is the action on the view model happening in a completely different class? The view model should control changes to its state; either internally or through bindings to a view. If something changing in HatEngine should result in a change to your view model then perhaps the relationship should be reversed. Meaning the view model should have a reference to the HatEngine class instead and perhaps the HatEngine class should raise an event (or events) that the view model uses to change any state necessary. – coding.monkey Aug 23 '21 at 07:57
  • Hi @coding.monkey. The same issue exist in either direction. If I change a property in HatEngine, how does the ViewModel know that the Property has changed? Thanks – Slip Aug 23 '21 at 18:59
  • There isn't really enough information about what role HatEngine fills in this environment. Is it a service? Is it another ViewModel (doesn't appear to be based on the provided code since it is not deriving from ```ObservableObject```). _Assuming_ it is intended to work as a service there are a couple of options: fire events directly from the service or use event aggregation to communicate between the two classes. Below I've taken a stab at the first option; however, more information would be useful to help arrive at the real solution. – coding.monkey Aug 23 '21 at 21:15

2 Answers2

0

If the purpose of HatEngine is to be a service that is used by MyViewModel, then something like the following be the start of getting what you need.

This example uses dependency injection via the constructor; this is common in MVVM and if you're not familiar with it, I would highly recommend looking into it further.

// define delegate for event to be fired from HatEngine instances
public delegate void HatEngineNotifyEventHandler(object sender, bool shouldShow);

// interface declaration for HatEngine - this is important for injecting mocks for unit testing
public interface IHatEngine
{
    event HatEngineNotifyEventHandler Notify;
    void SetShowRedHat(bool show);
}

// simple IHatEngine implementation
public sealed class HatEngine : IHatEngine
{
    public event HatEngineNotifyEventHandler Notify;

    public void SetShowRedHat(bool show) => OnNotify(show);

    private void OnNotify(bool shouldShow) =>
        Notify?.Invoke(this, shouldShow);
}

public class MyViewModel : ObservableObject
{
    private readonly IHatEngine _hatEngine;
    private bool _showRedHat;

    // MyViewModel consumes an IHatEngine instance and subscribes to its Notify event
    public MyViewModel(IHatEngine hatEngine = null)
    {
        // many MVVM frameworks include a DI container that should be used here
        // to resolve an IHatEngine instance; however, for simplicity for this
        // example just create HatEngine() directly
        _hatEngine = hatEngine ?? new HatEngine();

        // when the event is received, update ShowRedHat accordingly
        _hatEngine.Notify += (_, shouldShow) => ShowRedHat = shouldShow;
    }

    public bool ShowRedHat
    {
        get => _showRedHat;
        set => OnPropertyChanged(ref _showRedHat, value);
    }
}
coding.monkey
  • 206
  • 1
  • 6
  • Hi @coding.monkey. I implemented above and there is no change. I created the Interface and added the Delegate to the Interface. I linked up HatEngine to the IHatEngine and added recommended code. I added recommended code to MyViewModel. Finally, I set binding on the View to ShowRedHat. I see in this codeblock an underscore ' _hatEngine.Notify += (_, shouldShow) => ShowRedHat = shouldShow;'. Is this causing the issue? I tried changing this underscore to _showRedHat, but no success. – Slip Aug 25 '21 at 09:46
  • You'll need to spend some time debugging the code to find out what is happening. Try putting break points in several places (e.g. SetShowRedHat() in HatEngine, in the event handler in MyViewModel). The questions to look to answer while debugging could be: is the Notify event firing (is any code actually calling SetShowRedHat())?; is the OnPropertyChanged() method firing the NotifyPropertyChanged event. Finally, try simplifying the xaml binding... instead of binding to the visibility and using a converter, bind to the IsEnabled property that is already a bool. – coding.monkey Aug 25 '21 at 22:45
0

You can just bind an integer since Visibility is an Enum, check documentation since in some versions Hidden option is not available and Collapsed becomes 1, however normally you can just use these below:

Visible [0] - Display the element.

Hidden [1] Do not display the element, but reserve space for the element in layout.

Collapsed [2] Do not display the element, and do not reserve space for it in layout.