WPF: фильтрация элементов в ItemsControl и ContentControl

Я создал пользовательский элемент управления пользовательского интерфейса, который представляет представление календарного месяца. Элемент управления состоит из 42 границ, расположенных в сетке 7x6 (7 дней в неделе x 6 недель для отображения в месяц).

Затем я создал Appointment класс. У него есть свойство DateTime AppointmentDate, которое должно определять границу в моем элементе управления, в которой будет отображаться встреча.

Я хочу иметь возможность предоставить моему элементу управления пользовательского интерфейса коллекцию встреч, а затем элемент управления должен определять, какая граница будет содержать элементы встречи, а какая останется пустой.

Как лучше всего этого добиться? Я думал о следующем: добавить ItemsControl к каждой границе, находящейся под моим контролем, а затем привязать каждую из них к коллекции встреч. Затем я бы создал и применил фильтр к каждому из этих элементов управления элементами, чтобы показать или пропустить связанные встречи. Это разумно с точки зрения программирования, памяти и производительности? Есть ли лучший способ добиться этого?

Что, если я хочу, чтобы на каждой границе проводилась только одна встреча (в коллекции не будет встреч с одинаковой датой встречи)? Следует ли заменить ItemsControl на ContentControl? Можно ли применить фильтрацию к ContentControls, и если да, то как?

Спасибо, что помогли мне.


person Boris    schedule 25.03.2011    source источник


Ответы (3)


Я бы, наверное, по-другому подошел ко всему этому. Я бы создал коллекцию для дней, и каждый день имел бы целочисленное свойство для даты, недели, дня недели и коллекции для встреч.

Затем я бы создал ItemsControl, привязанный к этой коллекции.

Я бы сделал ItemsControl.PanelTemplate сеткой с 7 столбцами и 6 строками, а ItemsControl.ItemTemplate - рамкой, содержащей дату и ListBox или ItemsControl для хранения встреч. Я бы настроил ItemsControl.ItemStyle так, чтобы Grid.Row и Grid.Column были привязаны к Week и DayOfWeek соответственно.

person Rachel    schedule 25.03.2011
comment
Какой элемент управления вы бы использовали для сохранения встреч за день, если в день может быть только одно посещение? ListBox полезен для сбора дневных встреч, но мне не хватает контейнера для одного объекта встречи. ContentControl тот, кого я ищу? - person Boris; 25.03.2011
comment
Зачем вам то или другое? ListBox с одним элементом должен выглядеть так же, как ContentControl с одним элементом. Единственное, что может отвлекать, - это курсор выбора ListBox, но вы можете использовать PropertyTrigger и стиль, который убирается, когда в списке только один элемент. Но также похоже, что вы не знаете, нужен ли вам список встреч на день. Если у вас может быть только одна встреча в день, вам все равно не следует использовать ItemsControl (или производный класс). В этом случае можно использовать любой производный класс ContentControl. - person Dave White; 25.03.2011
comment
Спасибо, что разъяснил это, Дэйв. - person Boris; 25.03.2011
comment
@Rachel: Не могли бы вы подумать о коллекции Days, пожалуйста? Если бы мой элемент управления пользовательского интерфейса представлял бы месяц просмотра календаря, я предполагаю, что вы не имели в виду заполнение коллекции Days 365 элементами коллекции в год. Спасибо, что помогла мне с этим, Рэйчел! - person Boris; 25.03.2011
comment
@Boris Коллекция Days будет содержать данные за текущий месяц, который просматривается. Если бы пользователь переключил «Месяцы», я бы загрузил дни и встречи следующего месяца. Для одной встречи в день я бы либо стилизовал ItemsTemplate элемента ItemsControl, но хотел, чтобы встреча выглядела, либо я бы использовал ContentControl с Content, привязанным к встрече, а затем использовал DataTemplate, чтобы определить, как должна отображаться встреча. . - person Rachel; 25.03.2011
comment
@ Рэйчел, круто! Теперь все кажется намного проще. Спасибо. - person Boris; 26.03.2011
comment
@Rachel, просто быстрый вопрос: в своем ответе, когда вы писали ItemsControl.ItemTemplate в границу, содержащую Date и ListBox или ItemsControl для хранения встреч, по дате вы имели в виду свойство зависимости или TextBlock? - person Boris; 26.03.2011
comment
@Boris Я просто имел в виду TextBlock, содержащий дату дня - person Rachel; 27.03.2011

Я смоделировал подход, отличный от того, что вы предлагали. Это упрощено, и я сделал пару предположений, но давайте попробуем.

Вместо использования сетки и сопоставления дней в сетке давайте воспользуемся WrapPanel и просто поместим в нее дочерних элементов, каждый из которых представляет день.

В свой App.xaml.cs вы можете поместить код, который создаст объект Day.

public class Day
{
    public DateTime Date { get; set; }
    public List<Appointment> Appointments { get; set; }
}

public partial class App : Application
{
    protected override void OnActivated(EventArgs e)
    {
        base.OnActivated(e);

        var daysCollection = new List<Day>();
        for (int i = 1; i <= 30; i++)
        {
            daysCollection.Add(new Day
                {
                    // arbitrary sample data
                    Date = new DateTime(2011, 04, i),
                    Appointments =
                        new List<Appointment>
                            {
                                new Appointment
                                    {
                                        Date = new DateTime(2011, 04, i),
                                        Description = "Some descriptive text"
                                    }
                            }
                });
        }

        this.Properties.Add("DaysCollection", daysCollection );
    }
}

Теперь у нас есть набор дней. Назначения не важны для этой части выборки.

Теперь мы создаем простой календарь UserControl и привязываем его к CalendarViewModel.

<UserControl x:Class="DaysCalendarBinding.Views.Calendar"
             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" 
             mc:Ignorable="d" 
             Height="210" Width="210">
    <WrapPanel x:Name="wrapPanel" Orientation="Horizontal" 
               ItemHeight="30" ItemWidth="30" 
               Loaded="wrapPanel_Loaded">
    </WrapPanel>
</UserControl>

ViewModel

public class CalendarViewModel
{
    public CalendarViewModel()
    {

    }

    public CalendarViewModel(IEnumerable<Day> inputDays)
    {
        // determine first day of the month passed in
        var firstDate =
            (from day in inputDays 
             orderby day.Date.Day 
             select day.Date).First();
        var todayIs = firstDate.DayOfWeek;
        var valueOfToday = (int) todayIs;

        // create this many blank day children
        DaysInMonth = new List<Day>();
        for (int i = valueOfToday; i > 0; i--)
        {
            // the start of some cheeze. I know. It's a sample.
            DaysInMonth.Add(new Day { Date = new DateTime(1,1,1) });
        }

        // add the rest ofthe days in to the collection
        foreach(var day in inputDays)
        {
            DaysInMonth.Add(day);
        }
    }

    public List<Day> DaysInMonth { get; private set; }
}

С обработчиком событий, когда загружается wrapPanel

private void wrapPanel_Loaded(object sender, RoutedEventArgs e)
{
    foreach (var day in ((CalendarViewModel)DataContext).DaysInMonth)
    {
        wrapPanel.Children.Add(
                 new DayView { 
                        DataContext = new DayViewModel(day) });
    }
}

Теперь мы создаем элементы управления DayViewModel и DayView, которые мы создаем и добавляем в WrapPanel.

<UserControl x:Class="DaysCalendarBinding.Views.DayView"
             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"
             mc:Ignorable="d" 
             d:DesignHeight="30" d:DesignWidth="30">
    <Border BorderBrush="Black" BorderThickness="1">
        <StackPanel Height="30" Width="30" Background="AliceBlue">
            <TextBlock FontSize="7"  Text="{Binding DayDate}"/>
        </StackPanel>
    </Border> </UserControl>

ViewModel

public class DayViewModel
{
    private Day innerDay;

    public DayViewModel() {}

    public DayViewModel(Day day)
    {
        innerDay = day;
    }

    public string DayDate
    {
        get
        {
            // I know this is a cheesy approach. It's a sample. :)
            if (innerDay.Date.Year != 1)
                // this only intended to demonstrate some content
                return innerDay.Date.DayOfWeek.ToString().Remove(3) +
                       "   " + innerDay.Date.Day;
            return string.Empty;
        }
    }
}

Теперь, наконец, наше главное окно, в которое мы добавляем элемент управления календарем, добавляем CalendarViewModel и, надеюсь, когда мы нажимаем F5, он отображается для вас. :)

<Window x:Class="DaysCalendarBinding.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:Views="clr-namespace:DaysCalendarBinding.Views" Title="Calendar Demo" Height="350" Width="525">
    <Grid>
        <Views:Calendar x:Name="calendarControl"></Views:Calendar>
    </Grid>
</Window>

Код программной части в MainWindow.xaml.cs

protected override void OnActivated(EventArgs e)
{
    base.OnActivated(e);
    calendarControl.DataContext = 
                          new CalendarViewModel((IEnumerable<Day>)Application
                              .Current
                              .Properties["DaysCollection"]);
}

Возможно, я сделал пару ошибок, перенеся это сюда из своего решения. Но я надеюсь, что это передает идею. Для меня это выглядит так.

Календарь марта

Скриншот мартовского календаря

Апрель календарь

Скриншот апрельского календаря

Теперь пришло время собрать все это вместе, чтобы это работало на вас. Это просто демонстрирует технику. Представить значимый контроль не должно быть так сложно.

Ваше здоровье.

person Dave White    schedule 25.03.2011
comment
Отличный пример, +1 от меня тоже. В конце концов, я бы предпочел избегать WrapPanel как личного выбора, но, тем не менее, это очень хороший пример. Ваше здоровье! - person Boris; 26.03.2011

Чтобы проконсультировать вас по первому вопросу, я направлю вас к CollectionViewSource и укажите, что

Когда пользователь связывает свойство WPF с коллекцией данных, WPF автоматически создает представление для обертывания коллекции и привязывает свойство к представлению, а не к необработанной коллекции. Источник

Это должно помочь вам разделить проблемы между всем набором данных и видимой частью.

Что касается вашего второго вопроса, после того, как вы выбрали логику фильтрации, вы можете лучше смоделировать свой элемент управления для работы с ним. Поскольку я не вижу ваш код и мало знаю о том, что вы делаете, это довольно общий вариант. Я бы порекомендовал привязать элемент управления к одной встрече (чтобы он отображал только одну встречу, как вы просили). Если он пустой или пустой, ничего не показывать в эту дату. Это позволит вам манипулировать данными (моделью), а не элементом управления (представлением), но при этом добиться желаемого результата.

person Ragepotato    schedule 25.03.2011
comment
если я использую CollectionViewSource для каждого из списков, содержащих встречи на определенный день, и применяю фильтрацию к ним, каково будет влияние на производительность и объем памяти? Поскольку будет 42 списка, нацеленных на источник представления коллекции, которые будут отфильтрованы 42 раза, меня мало беспокоит взаимодействие с пользователем. Что вы думаете? - person Boris; 26.03.2011