2

Update

I edited the code below to match the suggestions and it works correctly now.

I've seen several stack overflow questions similar to this one, but I haven't quite been able to put it all together. I have the following xaml code.

<UserControl x:Class="AuditEfficiencyMVVM.View.AuditTestsMain"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:AuditEfficiencyMVVM.View"
         xmlns:viewmodel="clr-namespace:AuditEfficiencyMVVM.ViewModel"
         mc:Ignorable="d" 
         d:DesignHeight="500" d:DesignWidth="1000">

<UserControl.DataContext>
    <viewmodel:AuditTests/>
</UserControl.DataContext>

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>

    <Grid.RowDefinitions>
        <RowDefinition Height="50"/>
        <RowDefinition/>
        <RowDefinition Height="50"/>
    </Grid.RowDefinitions>

    <ListView Grid.Row="1" Grid.Column="0" Margin="10" ItemsSource="{Binding Path=Tests}">
        <ListView.View>
            <GridView>
                <GridViewColumn>
                    <GridViewColumn.CellTemplate>
                        <DataTemplate>
                            <CheckBox Name="TestSelected" IsChecked="{Binding Path=Selected, Mode=TwoWay}" Command="{Binding Path=TestSelected, RelativeSource={RelativeSource AncestorType=ListView}}"/>
                        </DataTemplate>
                    </GridViewColumn.CellTemplate>
                </GridViewColumn>
                <GridViewColumn Header="Test Type" DisplayMemberBinding="{Binding Type, Mode=OneWay}"/>
                <GridViewColumn Header="Progress">
                    <GridViewColumn.CellTemplate>
                        <DataTemplate>
                            <ProgressBar Name="TestProgress" Width="50" Height="20" Value="{Binding Progress, Mode=OneWay}"/>
                        </DataTemplate>
                    </GridViewColumn.CellTemplate>
                </GridViewColumn>
                <GridViewColumn Header="Status" DisplayMemberBinding="{Binding Status, Mode=OneWay}"/>                    
            </GridView>
        </ListView.View>

        <ListView.GroupStyle>
            <GroupStyle>
                <GroupStyle.ContainerStyle>
                    <Style TargetType="{x:Type GroupItem}">
                        <Setter Property="Template">
                            <Setter.Value>
                                <ControlTemplate>
                                    <Expander IsExpanded="True">
                                        <Expander.Header>
                                            <TextBlock FontWeight="Bold" FontSize="14" Text="{Binding Name}"/>
                                        </Expander.Header>
                                        <ItemsPresenter/>
                                    </Expander>
                                </ControlTemplate>
                            </Setter.Value>
                        </Setter>
                    </Style>
                </GroupStyle.ContainerStyle>
            </GroupStyle>
        </ListView.GroupStyle>
    </ListView>

    <ListView Grid.Row="1" Grid.Column="1" Margin="10" ItemsSource="{Binding Path=Files}">
        <ListView.View>
            <GridView>
                <GridViewColumn Header="File Type" DisplayMemberBinding="{Binding Type, Mode=OneWay}"/>
                <GridViewColumn Header="File Location" Width="250">
                    <GridViewColumn.CellTemplate>
                        <DataTemplate>
                            <TextBox Text="{Binding Path=Location, Mode=TwoWay}" Width="225"/>
                        </DataTemplate>
                    </GridViewColumn.CellTemplate>
                </GridViewColumn>                    
                <GridViewColumn>
                    <GridViewColumn.CellTemplate>
                        <DataTemplate>
                            <Button Width="30" Height="20">...</Button>
                        </DataTemplate>
                    </GridViewColumn.CellTemplate>
                </GridViewColumn>
            </GridView>
        </ListView.View>
    </ListView>

    <Button Grid.Row="2" Grid.Column="1" HorizontalAlignment="Right" Margin="10" Width="50" Height="30">Run</Button>
</Grid>
</UserControl>

Here is my code behind

public class AuditTests : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private RelayCommand _testSelected;

    private void AddTest()
    {
        MessageBox.Show("Success");
    }

    public RelayCommand TestSelected
    {
        get
        {
            return _testSelected;
        }
        private set
        {
            if (_testSelected != value)
            {
                _testSelected = value;
                RaisePropertyChanged("TestSelected");
            }                
        }
    }

    private void RaisePropertyChanged(string property)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(property));
        }
    }

    public AuditTests()
    {
        TestSelected = new RelayCommand(AddTest);
    }

    public ObservableCollection<Model.File> Files
    {
        get;
        set;
    }

    public ObservableCollection<Model.Test> Tests
    {
        get;
        set;
    }

    public void LoadFiles()
    {
        ObservableCollection<Model.File> files = new ObservableCollection<Model.File>();

        foreach (Model.Test test in Tests)
        {
            foreach (Enums.FileType type in test.ExpectedSources)
            {
                Boolean containsType = false;
                foreach (Model.File file in files)
                {
                    if (file.Type == type)
                    {
                        containsType = true;
                        break;
                    }
                }

                if (!containsType)
                {
                    files.Add(new Model.File { Type = type, Location = "", Tests = new List<Enums.TestType> { test.Type } });
                }
                else
                {
                    files.Where(t => t.Type == type).First().Tests.Add(test.Type);
                }
            }
        }

        Files = files;
    }

    public void LoadTests()
    {
        ObservableCollection<Model.Test> tests = new ObservableCollection<Model.Test>();

        foreach (var prop in Enum.GetValues(typeof(Enums.TestType)).Cast<Enums.TestType>().ToList())
        {
            tests.Add(new Model.Test { Category = prop.GetCategory(), Type = prop, Progress = 0, Selected = true, Status = Enums.TestStatus.NotStarted, ExpectedSources = prop.GetExpectedFiles() });
        }

        Tests = tests;
    }        
}
}

From what I've read this seems like it should work, but when I check/uncheck the check box the message box is not activated. What am I missing here to get the check/uncheck command to work?

Tim Hutchison
  • 3,483
  • 9
  • 40
  • 76
  • That's not a codebehind, that's a model or something. How do you connect the `AuditTests` class with the `AuditTestsMain` UserControl? – 15ee8f99-57ff-4f92-890c-b56153 Apr 14 '17 at 18:07
  • @EdPlunkett I guess I don't now that I look at it. I connect the List Views to the Observable Collections, but it doesn't look like I connect the view as a whole to the `AuditTests`. Would that be a DataContext thing? – Tim Hutchison Apr 14 '17 at 18:10
  • Yes. It looks to me like `AuditTests` wants to be a viewmodel. It should implement `INotifyPropertyChanged`, and when you use your UserControl, it should have an instance of `AuditTests` for its DataContext. This is in addition to what ASh points out below. – 15ee8f99-57ff-4f92-890c-b56153 Apr 14 '17 at 18:11
  • @EdPlunkett How would I set the Data Context Correctly? I've got `DataContext = "{}"` in the ``, but I'm not sure how to specify the file. `AuditTestsMain.xaml` is in View/ while `AuditTests.cs` is in ViewModel/ – Tim Hutchison Apr 14 '17 at 18:24
  • That's going to depend on who's responsible for creating `AuditTests`. It doesn't matter what folders the classes are defined in. How are you using `AuditTestsMain`? Do you just want to toss an instance of it in anywhere and have it do its thing? Or does some other viewmodel own a copy of `AuditTests`, and it wants to display that in its own view? – 15ee8f99-57ff-4f92-890c-b56153 Apr 14 '17 at 18:26
  • @EdPlunkett `AuditTestsMain` will be set in `MainWindow.xaml` which is the only place it will be used. `MainWindow` may replace `AuditTestsMain` with other content in the future, but right now it's just the one user control. – Tim Hutchison Apr 14 '17 at 18:28

2 Answers2

2

TestSelected command is a property AuditTests object. DataContext of CheckBox is Model.Test object. They are on different levels. You can bind command to a property from ListView DataContext with RelativeSource parameter:

Command="{Binding Path=DataContext.TestSelected, 
                  RelativeSource={RelativeSource AncestorType=ListView}}"
ASh
  • 34,632
  • 9
  • 60
  • 82
1

First, I'd make AuditTests implement INotifyPropertyChanged, and raise PropertyChanged in all its property setters when their values change. There's a lot of documentation out there on doing that.

Second, you've got to give the user control a copy of it. I can't tell if you gave your UserControl any properties of its own that MainWindow may want to put bindings on; if that's the case, it's a more complicated question. But the simplest thing is this:

public AuditTestsMain()
{
    InitializeComponent();

    //  Now its parent view can't bind this guy's properties to properties of the 
    //  parent's view's viewmodel, because we've broken DataContext inheritance. 
    DataContext = new ViewModels.AuditTests();
}

Then look at ASh's answer for how to bind the command. That's another DataContext thing: The thing there is that the listview items are Model.Test instances. So inside that DataTemplate for the items, bindings bind to the properties of Model.Test by default, because that's the DataContext. But the command is not a property of Model.Test; it's a property of AuditTests. AuditTests is the UserControl's DataContext, and therefore it's the ListView's DataContext as well. Controls inherit the parent's DataContext unless something interferes with that -- like the line I'm adding to your AuditTestsMain constructor, or like the way the ListView creates child items which have the listview's items as their DataContext.