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?

6
  • That's not a codebehind, that's a model or something. How do you connect the AuditTests class with the AuditTestsMain UserControl? Commented Apr 14, 2017 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? Commented Apr 14, 2017 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. Commented Apr 14, 2017 at 18:11
  • @EdPlunkett How would I set the Data Context Correctly? I've got DataContext = "{}" in the <UserControl>, but I'm not sure how to specify the file. AuditTestsMain.xaml is in View/ while AuditTests.cs is in ViewModel/ Commented Apr 14, 2017 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? Commented Apr 14, 2017 at 18:26

2 Answers 2

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}}"
Sign up to request clarification or add additional context in comments.

Comments

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.