简体   繁体   中英

Recognizing checkbox created by a template in a fired event

I've created an unfoldable list of checkboxes like so.

<Expander x:Name=...>
  <ListBox ItemsSource="{x:Static local:MainWindow.AllTypes}">
    <ListBox.ItemTemplate>
      <DataTemplate>
        <CheckBox Content="{Binding Name}"
                  Checked="ToggleButton_OnToggled"
                  Unchecked="ToggleButton_OnToggled"/>
      </DataTemplate>
    </ListBox.ItemTemplate>
  </ListBox>
</Expander>

I also have a method with the signature below.

private void FilterStuffOut(String condition)
{
  CollectionViewSource source 
    = new CollectionViewSource { Source = dataGrid.ItemsSource };
  ICollectionView view = source.View;
  view.Filter = element => BringItOut(element, condition);
  dataGrid.ItemsSource = view;
}

I'm unsure (and poking around with intellisense both in sender and eventArgs gave me nothing) how to get to know which checkbox is the firey one. Where should I look for it in the method below?

private void ToggleButton_OnToggled(
  Object sender, RoutedEventArgs eventArgs) { ... }

You would typically write it like shown below, explicitly not using the as operator, but casting to the desired types. This is because you expect those types, and any other type should result in a runtime error, ie an InvalidCastException .

private void ToggleButton_OnToggled(object sender, RoutedEventArgs eventArgs)
{
    var element = (FrameworkElement)sender;
    var myType = (MyType)element.DataContext;

    // do something with myType.MyValue
}

If you would need properties of more derived types, eg ToggleButton.IsChecked , you would use that type instead of FrameworkElement.

As promised.

I tend to use Movies for my examples, so please excuse my habit. I wasn't certain exactly what your UI looked like, or what you were trying to achieve here, but I think I understood the general idea.

If you're not familiar with the MVVM design pattern, I'd advise working through this tutorial.

Here we go.

These are my models:

//This is a base class which handles our notify property changed stuff which will update the UI
//when properties change.
public class NotifyPropertyChangedBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged([CallerMemberName]string propertyName = null)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

The class above is the base class for all models and sometimes view models if the view model requires it. It's pretty useful and I'd advise that you implement something like this in all of your MVVM applications.

public class Filter : NotifyPropertyChangedBase
{
    public event EventHandler OnEnabledChanged;

    public string Genre { get; set; }

    private bool _IsEnabled;

    public bool IsEnabled
    {
        get { return _IsEnabled; }
        set 
        { 
            _IsEnabled = value;
            OnPropertyChanged();

            if (OnEnabledChanged != null)
                OnEnabledChanged(this, new EventArgs());
        }
    }

    public Filter(string genre)
    {
        this.Genre = genre;
    }
}

public class Movie
{
    //We don't need to implement INotifyPropertyChanged here
    //because these values will never change.
    public string Name { get; set; }
    public string Genre { get; set; }
}

The models above represent movies, and a filter for the genre of a movie. All models do not "do" anything, they simply represent data.

And here is the ViewModel:

public class MovieViewModel : NotifyPropertyChangedBase
{
    private ObservableCollection<Movie> _FilteredMovies;

    public ObservableCollection<Movie> FilteredMovies
    {
        get { return _FilteredMovies; }
        set 
        {
            _FilteredMovies = value;

            //Need to implement INotifyPropertyChanged here because 
            //I am instantiating a new observable collection in the enabled changed
            //method. This will refresh the binding on the DataGrid.
            OnPropertyChanged();
        }
    }

    public ObservableCollection<Movie> Movies { get; set; }
    public ObservableCollection<Filter> Filters { get; set; }

    public MovieViewModel()
    {
        this.Movies = new ObservableCollection<Movie>();
        this.Filters = new ObservableCollection<Filter>();

        #region Sample Data

        this.Movies.Add(new Movie()
            {
                Name = "Movie Action",
                Genre = "Action"
            });

        this.Movies.Add(new Movie()
        {
            Name = "Movie Romance",
            Genre = "Romance"
        });

        this.Movies.Add(new Movie()
        {
            Name = "Movie Comedy",
            Genre = "Comedy"
        });

        this.Filters.Add(new Filter("Action"));
        this.Filters.Add(new Filter("Romance"));
        this.Filters.Add(new Filter("Comedy"));

        foreach (Filter filter in this.Filters)
            filter.OnEnabledChanged += filter_OnEnabledChanged;

        #endregion
    }

    void filter_OnEnabledChanged(object sender, EventArgs e)
    {
        var filteredMovies = (from m in this.Movies
                                join f in this.Filters on m.Genre equals f.Genre
                                where f.IsEnabled
                                select m).ToList();

        this.FilteredMovies = new ObservableCollection<Movie>(filteredMovies);
    }
}

It has a collection of movies, and a collection of filters. When a checkbox is selected, the OnEnabledChanged method will be called and set the FilteredMovies property to that of the selected filters. This in turn will call the notify property changed code and update the UI.

Here is the UI:

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:ViewModels="clr-namespace:WpfApplication1"
    Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
    <ViewModels:MovieViewModel/>
</Window.DataContext>
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <Expander>
        <ItemsControl ItemsSource="{Binding Filters}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <CheckBox IsChecked="{Binding IsEnabled}"
                              Content="{Binding Genre}"/>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Expander>

    <DataGrid Grid.Row="1"
              ItemsSource="{Binding FilteredMovies}"/>
</Grid>

Similar to your implementation, there is a DataGrid which is bound to the FilteredMovies property in the ViewModel, and the list of Filters is represented as a list of CheckBox objects.

Like I mentioned earlier, I wasn't exactly sure what you were trying to implement, but I think this was something of what you were trying to do.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM