kformeck
kformeck

Reputation: 1923

MVVM Binding Issue - Another Noob-ish Issue

So here I am again, asking a very similar question to yesterday. I re-factored my project in order to better follow the MVVM pattern. Now my binding is no longer working as it was yesterday. I am trying to bind the visibility of a dock panel to a button. Here is some of my code:

ViewModel:

public class SelectWaferButtonViewModel : INotifyPropertyChanged
{
    private bool isClicked;

    public SelectWaferButtonViewModel()
    {
        isClicked = false;
    }

    public bool IsControlVisible
    {
        get
        {
            return isClicked;
        }
        set
        {
            isClicked = value;
            OnPropertyChanged("IsControlVisible");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public void OnButtonClick()
    {
        if (isClicked)
        {
            IsControlVisible = false;
        }
        else
        {
            IsControlVisible = true;
        }
    }
    protected virtual void OnPropertyChanged(string property)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(property));
        }
    }
}

XAML:

<Window.Resources>
     <local:BoolToVisibilityConverter x:Key="BoolToVisConverter"/>
     <local:SelectWaferButtonViewModel x:Key="SelectWaferButton" />
     <local:WaferTrackerWindowViewModel x:Key="WindowViewModel" />
</Window.Resources>
<DockPanel
     Name="tvwDockPanel"
     DataContext="{StaticResource SelectWaferButton}"
     Width="225"
     Visibility="{Binding IsControlVisible, Mode=TwoWay, 
                  FallbackValue=Collapsed, 
                  Converter={StaticResource BoolToVisConverter}}"
     DockPanel.Dock="Left">
 </DockPanel>

My BoolToVisConverter:

public class BoolToVisibilityConverter : IValueConverter
{
    public BoolToVisibilityConverter() { }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        bool bValue = (bool) value;
        if (bValue)
        {
            return Visibility.Visible;
        }
        else
        {
            return Visibility.Collapsed;
        }
    }
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        Visibility visibility = (Visibility) value;
        if (visibility == Visibility.Visible)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}

I apologize for a question that is similar to yesterday, but I am struggling with this MVVM stuff since I am quite new to WPF. Any help will be much appreciated.

EDIT: Here is some extra code snippets for further reference:

public class WaferTrackerWindowViewModel :INotifyPropertyChanged
{
    private SelectWaferButtonViewModel btnSelectWaferViewModel;
    public event PropertyChangedEventHandler PropertyChanged;
    private DelegateCommand exitCommand;
    private DelegateCommand expandPanelCommand;
    private DelegateCommand selectWaferCommand;

    public WaferTrackerWindowViewModel()
    {
        this.InstantiateObjects();
        initThread.RunWorkerAsync();
    }

    public string SelectedWafer
    {
        get
        {
            return selectedWafer;
        }
        set
        {
            selectedWafer = value;
        }
    }
    public ICommand ExitCommand
    {
        get
        {
            if (exitCommand == null)
            {
                exitCommand = new DelegateCommand(Exit);
            }
            return exitCommand;
        }
    }
    public ICommand ExpandPanelCommand
    {
        get
        {
            if (expandPanelCommand == null)
            {
                expandPanelCommand = new DelegateCommand(ExpandPanel);
            }
            return expandPanelCommand;
        }
    }
    public ICommand SelectWaferCommand
    {
        get
        {
            if (selectWaferCommand == null)
            {
                selectWaferCommand = new DelegateCommand(SelectWafer);
            }
            return selectWaferCommand;
        }
    }

    private void InstantiateObjects()
    {
        btnSelectWaferViewModel = new SelectWaferButtonViewModel();
        initThread = new BackgroundWorker();
    }
    private void ExpandPanel()
    {
        btnSelectWaferViewModel.OnButtonClick();
    }
    private void SelectWafer()
    {
       //Does Nothing Yet
    }
    private void Exit()
    {
        Application.Current.Shutdown();
    }
    private void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    private void InitThread_DoWork(object sender, DoWorkEventArgs e)
    {
        TreeViewPresenter tvwPresenter = new TreeViewPresenter();
        tvwPresenter.WaferList = DataLibrary.GetWaferList();
    }
    private void InitThread_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        tvwPresenter.TreeView.DataContext = tvwPresenter.ProcessesAndWafers;
        tvwPresenter.WaferListCache = tvwPresenter.ProcessesAndWafers;
        tvwPresenter.ProcessArray = tvwPresenter.WaferListCache.ToArray();
    }
}

When the "expand panel" button gets clicked, it calls the ExpandPanel command, which routes the execution to the method "private void ExpandPanel()" in this same class. Then, in the ExpandPanel() method, it calls the OnButtonClick() method on the btnSelectWaferViewModel object, which will change the IsControlVisible property. This change should then be reflected onto the bound dock panel, but this is not happening

Kyle

Upvotes: 0

Views: 375

Answers (2)

sircodesalot
sircodesalot

Reputation: 11439

(1) ViewModel should be in the Window.DataContext section, not the Window.Resources section.

(2) In your view model, make your IsControlVisible property a System.Windows.Visibility, rather than a Boolean, then you don't need a converter.

(3) I don't see any way for OnButtonClick to fire, and it really needs to be set up with ICommand interface.

(4) You don't need to implement ConvertBack because the Visibility property you're binding to is one way by definition. There is no way for the user to set the visibility to false.

(5) Don't mix accessing IsClicked and it's accessor IsControlVisible. Always use the Accessor in MVVM, because you run the risk of accidentally setting IsClicked which won't activate OnPropertyChanged.

All in all, you're pretty close. Make sure to keep an eye on your "Output" window, it will tell you if a binding is failing for some reason. But yeah, hang in there!

Upvotes: 2

Matt Burland
Matt Burland

Reputation: 45155

So when you do this:

<Window.Resources>
    <local:SelectWaferButtonViewModel x:Key="SelectWaferButton" />
</Window.Resources>

WPF will create a new instance of the SelectWaferButtonViewModel and add it to it's resources. You then bind to this by setting the DataContext using the StaticResource with the key.

However, if you are then creating another SelectWaferButtonViewModel in your code behind and linking up your command to that instance, then it's not the same instance, so changes to the properties of this unbound instance won't effect your UI. There are a couple of ways around it. You can either a) create a single SelectWaferButtonViewModel in the code behind as a property and then bind to that in XAML, or b) Declare your SelectWaferButtonViewModel in XAML as you currently have it and then retrieve that instance in your code behind, like this:

SelectWaferButtonViewModel swbvm = (SelectWaferButtonViewModel)this.FindResource("SelectWaferButton");

Edit: So after seeing your last edit, if you want to go with a) then I would suggest you expose btnSelectWaferViewModel as a property in your WaferTrackerWindowViewModel and then bind to that property with the DataContext of your Window set to the WaferTrackerWindowViewModel instance. So you end up with something like:

<DockPanel
    Name="tvwDockPanel"
    Width="225"
    Visibility="{Binding MyButton.IsControlVisible, 
        Converter={StaticResource BoolToVisConverter}}"
    DockPanel.Dock="Left">
</DockPanel>

and:

public class WaferTrackerWindowViewModel :INotifyPropertyChanged
{
    private SelectWaferButtonViewModel btnSelectWaferViewModel;
    public SelectWaferButtonViewModel MyButton 
    {
        get { return btnSelectWaferViewModel; }
        set
        {
            btnSelectWaferViewModel = value;
            OnPropertyChanged("MyButton");
        }
    }
    //......

Upvotes: 1

Related Questions