3
votes

I've got a basic page in a Windows 10 Universal App that I'm using the new binding pattern with.

I'm loading a ViewModel into a public property on the MainPage.xaml.cs code-behind. This ViewModel contains a bunch of properties that I am binding to properties on my controls and they work just fine.

public sealed partial class MainPage : Page
{
    public MainViewModel MainVM { get; set; }

    public MainPage()
    {
        this.MainVM = SimpleIoc.Default.GetInstance<MainViewModel>();
        this.InitializeComponent();
    }
... more stuff that isn't important ...
}

Now I want to bind to the SelectionChanged event on a ListView. I'm using the following in my ViewModel:

public void AccountsSelectionChanged(object sender, SelectionChangedEventArgs e)
{
    .... stuff ....
}

And this is in my XAML:

<ListView VerticalAlignment="Bottom"
          ItemsSource="{x:Bind MainVM.Accounts}"
          ItemTemplate="{StaticResource AccountItemTemplate}"
          SelectionMode="Single"
          SelectionChanged="{x:Bind MainVM.AccountsSelectionChanged}"
          Grid.Row="1">
    <ListView.Header>
        <TextBlock>Accounts</TextBlock>
    </ListView.Header>
</ListView>

Here's the crazy thing... for a while, this has worked! A few hours later, I started getting this error:

Error CS0122 'MainPage.MainPage_obj1_Bindings.MainPage_obj1_BindingsTracking.cache_MainVM' is inaccessible due to its protection level

It's very possible I made a change somewhere that caused this condition, but I have no idea where. If I remove the binding to the SelectionChanged event, then this error goes away. But it was WORKING before! I don't know what else to do. I've tried cleaning the solution and rebuilding without the binding, then putting it back in and it doesn't work.

I've verified that every possible class involved is both public and has a public constructor. Restarting VS2015 RC didn't help and neither did rebooting Windows 10 (build 10074).

EDIT - I have verified that the x:Bind pattern for events does work when I put the handler directly in code-behind on MainPage.xaml.cs with this XAML:

SelectionChanged="{x:Bind AccountsSelectionChanged}"

My viewmodel is public, public, public. I am not sure what I am missing.

Can anyone provide something else to try?

1
Of course the error is very weird and for that the solution is always weird testing and error, would you set the ListView x:FieldModifier public and all the SimpleIoc.Default.GetInstance<MainViewModel>(); are publics? - Juan Pablo Garcia Coello
Tried the ListView FieldModifier. Didn't help. Also, I created an instance of MainViewModel using the constructor directly instead of using the IoC container. No change. - Lee McPherson
Figured it out.... answer forthcoming... - Lee McPherson

1 Answers

0
votes

It was something rather strange. I've got two ListViews on my MainPage. On the first, I have an ObservableCollection of Account classes bound as the ItemsSource. I also have the SelectionChanged event bound to a handler on the same ViewModel. This is the event that was giving me problems.

On the other ListView, I want to display an ObservableCollection of Folder classes, a property of the selected Account.

<ListView VerticalAlignment="Top"
    Margin="0,48,0,0"
    ItemsSource="{x:Bind MainVM.SelectedAccount.Folders, Mode=OneWay}"
    ItemTemplate="{StaticResource FolderItemTemplate}"
    Grid.Row="0">
    <ListView.Header>
         <TextBlock>Folders</TextBlock>
    </ListView.Header>
 </ListView>

The SelectedAccount is set back in the event handler from the first ListView. I had read on a blog that the x:Bind command seemed to default to OneTime binding rather than OneWay, so I set the Mode as OneWay on the ItemsSource. This binding was going to change, so I assumed this was an appropriate setting.

However, this is what was causing all my problems! I removed the Mode=OneWay setting and my project compiled just fine. Strangely, I can leave in this Mode=OneWay setting and remove the event binding in the other ListView and it will compile. I just can't have both.

I don't know if this is going to affect my plans for this ListView but at least I can compile now.

EDIT - This is only half the answer... I can't do binding properly with these constraints. I need to figure out why this is happening.

EDIT 2 My workaround is to stop using that event and instead bind to the SelectedItem property on the ListView. The SelectionMode needs to be set to Single for this to work correctly. Also, SelectedItem outputs an object type, so I needed to setup a getter/setter that converts the object to the original Account class when passing it to the private variable. I can set this ListView property as Mode=TwoWay without any issues when I remove that binding to the event.

EDIT 3 There are actually two workarounds. In general, this one works always. Just handle the event in the codebehind. Since your ViewModel is a public property on the codebehind, then you can call a public method on the ViewModel from your event handler and put in the same parameters (or none).

//codebehind
//HeadersVM is my ViewModel  

private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        foreach (var item in e.AddedItems)
            HeadersVM.SelectedHeaders.Add((Models.MailHeader)item);
        foreach (var item in e.RemovedItems)
            HeadersVM.SelectedHeaders.Remove((Models.MailHeader)item);
    }

private void BackButton_Click(object sender, RoutedEventArgs e)
    {
        this.HeadersVM.GoBack();
    }

Otherwise, for a workaround specific to a ListView's SelectedItem you can just TwoWay bind to the SelectedItem property. Then, create a Converter whose ConvertBack method turns the object into your model.

public class ObjectToAccountConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        return value;
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        if (value != null)
            return (Models.Account)value;
        return value;
    }
}

//in App.xaml Resources
<converters:ObjectToAccountConverter x:Key="ObjectToAccountConverter "/>

//in your Page.xaml
<ListView VerticalAlignment="Top"
    Margin="0,48,0,0"
    ItemsSource="{x:Bind MainVM.Accounts}"
    ItemTemplate="{StaticResource AccountTemplate}"
    SelectedItem="{x:Bind MainVM.SelectedAccount, Mode=TwoWay, Converter={StaticResouce ObjectToAccountConverter }}"
    Grid.Row="0">
    <ListView.Header>
         <TextBlock>Folders</TextBlock>
    </ListView.Header>
 </ListView>

OR instead of creating the Converter to convert the object back into your model, you can just make the property in your viewmodel a generic object instead of your actual class:

 private Models.Account _selectedAccount = null;

 public object SelectedAccount
 {
    get { return _selectedAccount; }
    set
    {
        if (_selectedAccount != value)
        {
           _selectedAccount = (Models.Account)value;
           RaisePropertyChanged();
           RaisePropertyChanged("Folders");
        }
     }
  }