Как отобразить только несколько элементов коллекции в ItemsControl - Silverlight - PullRequest
2 голосов
/ 12 октября 2011

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

По сути, у меня есть ItemsControl, связанный с коллекцией объектов, содержащихся в модели. То, что я хотел бы сделать, это отобразить только некоторые из этих элементов, а затем добавить гиперссылку / кнопку для «просмотра более». Который будет отображать всю коллекцию предметов. Я надеялся, что смогу использовать VSM для оповещения о «свернутых» и «развернутых» состояниях, но у меня возникают проблемы, связанные с инициализацией списка. Поскольку привязка создается в XAML, я стараюсь не использовать Linq в коде для изменения вручную коллекции ItemsSource, что может быть решением в случае сбоя всего остального.

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

Заранее спасибо.

[ОБНОВЛЕНИЕ] - Это решение, которое я придумал после долгих мозговых штурмов (для тех, кто хочет сделать то же самое). Спасибо AnthonyWJones за идею.

Что я сделал, так это собрал общую «модель», которая действует как мост между исходной коллекцией модели и коллекцией «представления». Предполагаемая цель (для меня) состояла в том, чтобы расширить любой класс модели, сгенерированный службой RIA WCF, который может иметь комментарии, связанные с ним, используя тот же пользовательский интерфейс (элементы управления и шаблоны), поэтому ожидаемая коллекция - это EntityCollection, где T - это экземпляр Entity '

Все следующие классы объявлены в клиентском проекте Silverlight

Сначала немного сантехники:

// this is so we can reference our model without generic arguments
public interface ICommentModel : INotifyPropertyChanged
{
    Int32 TotalComments { get; }
    Int32 VisibleComments { get; }

    Boolean IsExpanded { get; set; }
    Boolean IsExpandable { get; }

    ICommand ExpandCommand { get; }

    IEnumerable Collection { get; }
}

// the command we'll use to expand our collection
public class ExpandCommand : ICommand 
{
    ICommentModel model;

    public ExpandCommand(ICommentModel model) {
        this.model = model;
        this.model.PropertyChanged += ModelPropertyChanged;
    }

    public bool CanExecute(object parameter) {
        return this.model.IsExpandable;
    }

    public void Execute(object parameter) {
        this.model.IsExpanded = !this.model.IsExpanded;
    }

    private void ModelPropertyChanged(object sender, PropertyChangedEventArgs e) {
        if (e.PropertyName == "IsExpandable")
            RaiseCanExecuteChanged();
    }

    private void RaiseCanExecuteChanged() {
        var execute = CanExecuteChanged;
        if (execute != null) execute(this, EventArgs.Empty);
    }

    public event EventHandler CanExecuteChanged;
}

// and finally.. the big guns
public class CommentModel<TEntity> : ICommentModel 
    where TEntity : Entity
{
    Boolean isExpanded;

    ICommand expandCommand;

    IEnumerable<TEntity> source;
    IEnumerable<TEntity> originalSource;


    public Int32 TotalComments { get { return originalSource.Count(); } }

    public Int32 VisibleComments { get { return source.Count(); } }

    public Boolean IsExpanded {
        get { return isExpanded; }
        set { isExpanded = value; OnIsExpandedChanged(); }
    }

    public Boolean IsExpandable {
        get { return (!IsExpanded && originalSource.Count() > 2); }
    }

    public ICommand ExpandCommand {
        get { return expandCommand; }
    }

    public IEnumerable Collection { get { return source; } }


    public CommentModel(EntityCollection<TEntity> source) {
        expandCommand = new ExpandCommand(this);

        source.EntityAdded += OriginalSourceChanged;
        source.EntityRemoved += OriginalSourceChanged;

        originalSource = source;
        UpdateBoundCollection();
    }


    private void OnIsExpandedChanged() {
        OnPropertyChanged("IsExpanded");
        UpdateBoundCollection();
    }

    private void OriginalSourceChanged(object sender, EntityCollectionChangedEventArgs<TEntity> e) {
        OnPropertyChanged("TotalComments");
        UpdateBoundCollection();
    }

    private void UpdateBoundCollection() {
        if (IsExpanded)
            source = originalSource.OrderBy(s => PropertySorter(s));
        else
            source = originalSource.OrderByDescending(s => PropertySorter(s)).Take(2).OrderBy(s => PropertySorter(s));

        OnPropertyChanged("IsExpandable");
        OnPropertyChanged("VisibleComments");
        OnPropertyChanged("Collection");
    }

    // I wasn't sure how to get instances Func<T,TRet> into this class 
    // without some dirty hacking, so I used some reflection to run "OrderBy" queries
    // All entities in my DataModel have 'bigint' Id columns
    private long PropertySorter(TEntity s) {
        var props = from x in s.GetType().GetProperties()
                    where x.Name == "Id"
                    select x;

        if (props.Count() > 0)
            return (long)props.First().GetValue(s, null);

        return 0;
    }


    protected virtual void OnPropertyChanged(string propName) {
        var x = PropertyChanged;
        if (x != null) x(this, new PropertyChangedEventArgs(propName));
    }

    public event PropertyChangedEventHandler  PropertyChanged;
}

И теперь нам нужно это использовать. WCF RIA Services генерирует классы, помеченные как частичные (я не знаю, есть ли ситуации, когда это не так, но из того, что я видел, это происходит). Поэтому мы расширим сгенерированный класс сущности, включив нашу новую модель.

// this must be inside the same namespace the classes are generated in
// generally this is <ProjectName>.Web
public partial class Timeline
{
    ICommentModel model;

    public ICommentModel CommentModel {
        get {
            if (model == null)
                model = new CommentModel<TimelineComment>(Comments);

            return model;
        }
    }
}

Теперь мы можем ссылаться на модель комментария в привязках, где класс Timeline является контекстом данных / привязки.

* 1 028 * Пример:
<UserControl x:Class="Testing.Comments"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="291" d:DesignWidth="382">

    <Border CornerRadius="2" BorderBrush="{StaticResource LineBrush}" BorderThickness="1">
        <Grid x:Name="LayoutRoot">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition/>
            </Grid.RowDefinitions>

            <StackPanel Visibility="{Binding Path=CommentModel.IsExpandable, Converter={StaticResource BooleanToVisibility}}">
                <HyperlinkButton 
                    FontSize="10" 
                    Command="{Binding Path=CommentModel.ExpandCommand}"
                    Background="{StaticResource BackBrush}">
                    <TextBlock>
                        <Run Text="View all"/>
                        <Run Text="{Binding Path=CommentModel.TotalComments}"/>
                        <Run Text="comments"/>
                    </TextBlock>
                </HyperlinkButton>
                <Rectangle Height="1" Margin="0,1,0,0" Fill="{StaticResource LineBrush}" VerticalAlignment="Bottom"/>
            </StackPanel>

            <ItemsControl 
                Grid.Row="1"
                ItemsSource="{Binding Path=CommentModel.Collection}"
                ItemTemplate="{StaticResource CommentTemplate}" />
        </Grid>
    </Border>
</UserControl>

1 Ответ

2 голосов
/ 12 октября 2011

Это работа для вашей ViewModel.Внутренне у вас есть полная коллекция предметов.Однако первоначально ViewModel должен предоставлять IEnumerable, который сделает доступными лишь немногие.

ViewModel также предоставит свойство ICommand, называемое "ListAll".При выполнении эта команда заменит открытую IEnumerable на ту, которая перечислит все элементы.

Теперь это простой случай привязки ItemsControl, как вы уже делаете, и добавление кнопки «Еще», связывающей «Команда ListAll ".

...