首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何在SubTotal组中绑定ItemsControl

如何在SubTotal组中绑定ItemsControl
EN

Stack Overflow用户
提问于 2011-08-22 23:26:47
回答 2查看 1.4K关注 0票数 4

想象一下这个DTO类的类:

代码语言:javascript
复制
class LineItem : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public string Description { get; set; }

    private decimal m_Amount;
    public decimal Amount
    {
        get { return m_Amount; }
        set
        {
            if (m_Amount == value)
                return;
            m_Amount = value;
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs("Amount"));
        }
    }
}

像这样的装订:

代码语言:javascript
复制
<ItemsControl ItemsSource="{Binding LineItems}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <DockPanel>
                <TextBox Text="{Binding Amount}"
                        DockPanel.Dock="Right" Width="50" />
                <TextBlock Text="{Binding Description}" />
            </DockPanel>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

这看起来应该是:

现在,我想要一个在底部的总数。此外,我希望它更新时,金额变化。

就像这样:

代码语言:javascript
复制
<TextBlock HorizontalAlignment="Right"
            Text="{Binding LineItems, 
            Converter={StaticResource MyConverter}}" />

为此:

但是MyConverter是什么呢?那么,这是一个正确的方法吗?

我的问题是:

这不起作用,因为转换器只在第一次绑定时才被调用。我想要它反映用户的变化,我需要处理未知数量的LineItems。当然,我不是第一个这样做的。有办法吗?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2011-08-24 21:02:56

结果如下:

这是政务司司长:

代码语言:javascript
复制
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    // used to force databinding to refresh
    public int FakeProperty
    {
        get { return (int)GetValue(FakePropertyProperty); }
        set { SetValue(FakePropertyProperty, value); }
    }
    public static readonly DependencyProperty FakePropertyProperty =
        DependencyProperty.Register("FakeProperty", 
        typeof(int), typeof(MainWindow), new UIPropertyMetadata(0));

    private void TextBox_TextChanged(object sender, 
        System.Windows.Controls.TextChangedEventArgs e)
    {
        FakeProperty++;
    }

}

public class Item : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public string Family { get; set; }
    private int m_Value;
    public int Value
    {
        get { return m_Value; }
        set
        {
            if (m_Value == value)
                return;
            m_Value = value;
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs("Value"));
        }
    }
}

public class Items : ObservableCollection<Item>
{
    public Items()
    {
        this.Add(new Item { Family = "One", Value = 1 });
        this.Add(new Item { Family = "One", Value = 2 });
        this.Add(new Item { Family = "Two", Value = 3 });
        this.Add(new Item { Family = "Two", Value = 4 });
        this.Add(new Item { Family = "Two", Value = 5 });
        this.Add(new Item { Family = "Three", Value = 6 });
        this.Add(new Item { Family = "Three", Value = 7 });
    }
}

public class SumConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, 
                    object parameter, System.Globalization.CultureInfo culture)
    {
        var _Default = 0;
        if (values == null || values.Length != 2)
            return _Default;
        var _Collection = values[0] as System.Collections.IEnumerable;
        if (_Collection == null)
            return _Default;
        var _Items = _Collection.Cast<Item>();
        if (_Items == null)
            return _Default;
        var _Sum = _Items.Sum(x => x.Value);
        return _Sum;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, obje
                    ct parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

这个XAML:

代码语言:javascript
复制
xmlns:sort="clr-namespace:System.ComponentModel;assembly=WindowsBase"
xmlns:sys="clr-namespace:System;assembly=mscorlib" Name="This"

<Window.Resources>
    <local:Items x:Key="MyData" />
    <local:SumConverter x:Key="MyConverter" />
    <CollectionViewSource x:Key="MyView" Source="{StaticResource MyData}">
        <CollectionViewSource.GroupDescriptions>
            <PropertyGroupDescription PropertyName="Family" />
        </CollectionViewSource.GroupDescriptions>
        <CollectionViewSource.SortDescriptions>
            <sort:SortDescription PropertyName="Value" Direction="Ascending" />
        </CollectionViewSource.SortDescriptions>
    </CollectionViewSource>
</Window.Resources>

<StackPanel>

    <ItemsControl ItemsSource="{Binding Source={StaticResource MyView}}"
        Name="MyItemsControl">
        <ItemsControl.Resources>
            <Style TargetType="{x:Type GroupItem}">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type GroupItem}">
                            <StackPanel>
                                <!-- group header -->
                                <Border Padding="10,5,0,5" Margin="0,10,0,10" 
                                        Background="Gainsboro" CornerRadius="10">
                                    <TextBlock FontWeight="Bold" 
                                            Text="{Binding Name}" />
                                </Border>
                                <!-- group items -->
                                <ItemsPresenter Margin="10,0,0,0"/>
                                <!-- group footer -->
                                <Border BorderBrush="Black" 
                                            BorderThickness="0,.5,0,0"
                                            Margin="0,5,0,10">
                                    <TextBlock Width="100" 
                                            HorizontalAlignment="Right" 
                                            TextAlignment="Right" 
                                            Padding="0,0,5,0">
                                        <TextBlock.Text>
                                            <MultiBinding 
                                             StringFormat="{}{0:C}"
                                             Converter="{StaticResource MyConverter}">
                                                <Binding Path="Items" />
                                                <Binding Path="FakeProperty" 
                                                        ElementName="This"/>
                                            </MultiBinding>
                                        </TextBlock.Text>
                                    </TextBlock>
                                </Border>
                            </StackPanel>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </ItemsControl.Resources>
        <ItemsControl.GroupStyle>
            <GroupStyle ContainerStyle="{x:Null}" />
        </ItemsControl.GroupStyle>
        <ItemsControl.ItemTemplate>
            <DataTemplate DataType="ItemsPresenter">
                <DockPanel>
                    <TextBox Text="{Binding Value, StringFormat={}{0:C}, 
                        UpdateSourceTrigger=PropertyChanged}" 
                        TextChanged="TextBox_TextChanged" 
                        TextAlignment="Right" DockPanel.Dock="Right"
                        Width="100" />
                    <TextBlock Text="{Binding Value, 
                        StringFormat={}Value is {0}}" 
                        FontWeight="Bold" Foreground="DimGray" />
                </DockPanel>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>

    <!-- list footer -->
    <Border BorderBrush="Black" BorderThickness="0,.5,0,0" Margin="0,5,0,10">
        <TextBlock Width="100" HorizontalAlignment="Right" TextAlignment="Right" 
                   Padding="0,0,5,0" FontWeight="Bold">
            <TextBlock.Text>
                <MultiBinding Converter="{StaticResource MyConverter}" 
                              StringFormat="{}{0:C}">
                    <Binding Path="ItemsSource" ElementName="MyItemsControl" />
                    <Binding Path="FakeProperty" ElementName="This"/>
                </MultiBinding>
            </TextBlock.Text>
        </TextBlock>
    </Border>

</StackPanel>

搞清楚这是个多么可怕的噩梦!

票数 1
EN

Stack Overflow用户

发布于 2011-08-23 00:37:58

相反,您可以绑定到特定表示AverageAmount的属性(在“视图模型”上),并确保在PropertyChanged上为每个LineItem发送AverageAmount属性的更改通知,以允许模型计算值和UI重新获取新值。Maleak的例子正好表明

但是,仔细考虑了这样做的开销后,我将看到类似于BindableLinq奥普提斯 (或连续Linq)之类的东西,它们应该处理所有的依赖分析和更改通知。我们已经成功地使用了BindableLinq,尽管在这个时候,它并不是由创建它的人主动维护的。

编辑:

为了给出一个后端示例,而不使用我前面提到的那些库(它删除了处理集合和属性更改事件的管道):

代码语言:javascript
复制
public class ItemListViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private readonly ObservableCollection<ItemViewModel> _items = new ObservableCollection<ItemViewModel>();

    public ItemListViewModel()
    {
        _items.CollectionChanged += OnItemsChanged;
    }

    public ICollection<ItemViewModel> Items { get { return _items; } }

    private void OnItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                e.NewItems.Cast<ItemViewModel>().ToList().ForEach(iv => iv.PropertyChanged += OnItemPropertyChanged);
                break;
            case NotifyCollectionChangedAction.Remove:
                e.OldItems.Cast<ItemViewModel>().ToList().ForEach(iv => iv.PropertyChanged -= OnItemPropertyChanged);
                break;
            default:
                throw new NotImplementedException();
        }
    }

    private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "Value")
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs("AverageValue"));
        }
    }

    public double AverageValue
    {
        get { return Items.Average(iv => iv.Value); }
    }
}

public class ItemViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public string Family { get; set; }
    private int m_Value;
    public int Value
    {
        get { return m_Value; }
        set
        {
            if (m_Value == value)
                return;

            m_Value = value;
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs("Value"));
        }
    }
}

然后,XAML中的ItemsControl直接绑定到视图模型的Item属性,平均值绑定到AverageValue属性。现在它将处理所需的通知。

要在另一个级别添加分组,您必须引入另一个类"ItemGroupViewModel“,它将监视父项集合的更改。我将向所有项添加属性更改监听器,如果它们更改了它们的家庭属性,则从本地项集合中添加/删除。如果它们更改了它们的Value属性,则为AverageValue属性触发一个AverageValue。

注意: BindableLinq也支持分组操作。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/7154758

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档