JNate
JNate

Reputation: 103

RaisePropertyChanged not firing when function called from another viewmodel

I am having an issue updating the interface of my windows phone 7 app I am working on.

I have a MainPage.xaml and its MainViewModel.cs which handle how a child view usercontrol ChildView1.xaml appears in the MainPage. ChildView1 also has its ViewModel which is Child1ViewModel.cs. Basically a myToggleButton gets fired from the MainPage which will trigger a DataStateBehavior to go into a show or hide state to respectively show or hide the ChildView1.xaml. That DataStateBehavior is binded to the boolean CanShowView while the myToggleButton is binded to an Icommand named ButtonChecked which has a relay function SwitchVisibility that will reverse the CanShowView boolean to show or hide the view. All works well thanks to the RaisePropertyChanged of the MVVM light I used.

However, the ChildView1.xaml has a myCloseButton which is supposed to call the SwitchVisibility function from MainViewModel which would in turn revert the CanShowView and the RaisePropertyChanged in CanShowView should fire to have ChildView1 close, but the issue is that the view stays visible and never closes for some reason! I noticed through the debugger that the RaisePropertyChanged in CanShowView seems to not fire. I suppose that's where the problem lies. What are my doing wrong? How can I force it to fire? I used multiple RaisePropertyChanged but none seem to fire even though the degugger shows that the value in my function did change.

Here is my code. MainPage.xaml:

<phone:PhoneApplicationPage
    ...
>
    <phone:PhoneApplicationPage.DataContext>
        <Binding Path="Main" Source="{StaticResource Locator}"/>
    </phone:PhoneApplicationPage.DataContext>
    ...
    <Grid x:Name="LayoutRoot">
        ...
        <ToggleButton x:Name="myToggleButton" Command="{Binding Main.ButtonChecked, Source={StaticResource Locator}}" >
        <local:ChildView1 x:Name="myChildView1" Grid.Row="0" RenderTransformOrigin="0.5,0.5">
            <i:Interaction.Behaviors>
            <ec:DataStateBehavior Binding="{Binding Main.CanShowView, Source={StaticResource Locator}}" Value="True" TrueState="showChildView1" FalseState="hideChildView1"/>
        </i:Interaction.Behaviors>
        <local:ChildView1.RenderTransform>
            <CompositeTransform/>
        </local:ChildView1.RenderTransform>
        </local:ChildView1>
    </Grid>
</phone:PhoneApplicationPage>      

MainViewModel.cs :

public class MainViewModel : ViewModelBase
{
    public ICommand ButtonChecked { get; private set; }

    public MainViewModel()
    {
        ButtonChecked = new RelayCommand(() => SwitchVisibility()); 
    }

    public void SwitchVisibility()
    {
        CanShowView = !CanShowView;
        RaisePropertyChanged("CanShowView");
    }

    bool _canShowView = true;
    public bool CanShowView
    {
        get { return _canShowView; }
        set
        {
            if (value != _canShowView)
            {
                _canShowView = value;
                RaisePropertyChanged("CanShowView");
            }
        }
    }

}

ChildView1.xaml :

<UserControl x:Class="myApp.Views.ChildView1"
....
DataContext="{Binding Child1VM, Source={StaticResource Locator}}"
>

    <Grid x:Name="HomeGrid">
        <Button x:Name=myCloseButton Command="{Binding Child1VM.CloseChildViewCmd, Source={StaticResource Locator}}"/>
        ...
    </Grid>
</UserControl>

Child1ViewModel.cs :

namespace myApp
{
    public class Child1ViewModel : ViewModelBase
    {
        public ICommand CloseChildViewCmd { get; private set; }
        public Child1ViewModel()
        {
            CloseChildViewCmd = new RelayCommand(() => ExecuteCloseChildViewCmd());
        }

        public void ExecuteCloseChildViewCmd()
        {
            MainPage mainPage = new MainPage();
            MainViewModel mainVM = new MainViewModel();
            mainPage.DataContext = mainVM;
            mainVM.SwitchVisibility();

        }
    }
}

Note: I am only a beginner programmer so the way I am tackling the issue or the program itself might be questionable, hence I am open to any suggestions, and code samples are very welcome. I have been stuck with this issue for many days now after doing a lot of research and experimenting many things that never seemed to work. I took the time to write the issue in detail and am really hopeful that I could find a solution here. Let me know if ever there are more details you need to know. Thanks in advance.

EDIT: I read creating a new instance of the views is not a solution. How else can I access fire the MainView's functions and have properties raised?

EDIT: As suggested by OmegaMan, I am using static definitions to call my functions. So the updated MainViewModel.cs: public class MainViewModel : ViewModelBase { public ICommand ButtonChecked { get; private set; } private static MainViewModel Primary { get; set; }

    public MainViewModel()
    {
        ButtonChecked = new RelayCommand(() => SwitchVisibility()); 
        Primary = this;
    }

    public void SwitchVisibility()
    {
        CanShowView = !CanShowView;
        RaisePropertyChanged("CanShowView");
    }

    public static void SwitchViewStatic()
    {
        Primary.SwitchVisibility();
    }

    bool _canShowView = true;
    public bool CanShowView
    {
        get { return _canShowView; }
        set
        {
            if (value != _canShowView)
            {
                _canShowView = value;
                RaisePropertyChanged("CanShowView");
            }
        }
    }

}

The updated Child1ViewModel.cs: namespace myApp { public class Child1ViewModel : ViewModelBase { public ICommand CloseChildViewCmd { get; private set; } public Child1ViewModel() { CloseChildViewCmd = new RelayCommand(() => ExecuteCloseChildViewCmd()); }

        public void ExecuteCloseChildViewCmd()
        {
            MainViewModel.SwitchViewStatic();

        }
    }
}

The desired function in MainViewModel gets called by myCloseButton from the child view, but CanShowView still won't get raised to permit the DataStateBehavior listening to it, to update the UI... So I am still at an impass.

Upvotes: 2

Views: 1128

Answers (2)

ΩmegaMan
ΩmegaMan

Reputation: 31606

There is nothing wrong with having the children hold a reference to the main view model, but why is the control creating a new MainViewModel? Pass in the MainViewModel and call SwitchVisibility off it...or make a static method on the MainVM which will access the actuall VM and call instance SwitchVisiblity.

--- Update to show the static method access from the MainVM

public class MainViewModel : ViewModelBase
{
    private static MainViewModel Primary { get; set; }

    public MainViewModel()
    {
        Primary = this;
    }

    public static void SwitchViewStatic()
    {
        Primary.SwitchVisibility();
    }

    public void SwitchVisibility()
    {
    ...
    }

}

Upvotes: 1

Christopher Stevenson
Christopher Stevenson

Reputation: 2861

The problem is that you're sending a message to a new MainPage object and view model, not the existing one.

What you need is either a delegate or an event. The trick is connecting the Main Page to the Child Page.

If you need to keep them separate, you need an object to send the event along. This called an Event Aggregator, or messenger. You main viewmodel would need to subscribe to a CloseChild event that the child view model would send when the command is executed. The event aggregator transfers the message.

Note: Most MVVM frameworks implement a messenger of some kind that your view models can use to send and receive messages / events.

If you want to implement one yourself, take a look here for a way to create one.

Upvotes: 1

Related Questions