如何在 WPF 视图模型中使用关系表

how to use relational tables in a WPF viewmodel

我正在构建 N 层 WPF 应用程序。我想要零代码隐藏。

假设我有 3 个规范化相关 table 来记录销售交易。

交易

交易编号, 项目编号, 供应商编号, 价格

供应商:

SupplierId, 供应商名称

项目:

物品编号, 项目名称。

对于每个 table,我都有一个反映字段的基础 class。然后是根据需要填充基础对象集合的数据层。

我想在页面上有一个列表框,显示所有交易的列表,每行 1 个交易,行应该看起来像这样...

"Trainers  FootLocker   €99"

"Trousers  TopShop      €45"

"Coat      TopShop      €49"

如果我使用

<ListBox 
    ItemsSource="{Binding Path=Transactions}" 
    SelectedItem="{Binding CurrentTransaction}"

然后我得到来自交易 table 的 ID 行,而不是来自项目和供应商 table 的名称值。

鉴于我的交易集合仅填充了其他 table 的 ID,填充列表框的最佳方法是什么?

我想知道的一件事是,我的 Transactions Base 对象是否应该包含填充在那里的 Item 项目而不是 int ItemId?

交易基础模型:

using System;
using System.ComponentModel;
using PFT;
using PFT.Data;

namespace PFT.Base
{



    public class Transaction : INotifyPropertyChanged
    {

        public int Id { get; set; }

        private int _itemId;
        public int ItemId
        {
            get { return _itemId; }
            set { 
                _itemId = value;

                ItemData id = new ItemData();
                this.Item = id.Select(value);

                NotifyPropertyChanged("ItemId");
            }
        }

        private Item _item;

        public Item Item
        {
            get { return _item; }
            set { _item = value; }
        }


        private float _price;
        public float Price
        {
            get { return _price; }
            set { 
                _price = value;
                NotifyPropertyChanged("Price");
            }
        }

        private DateTime _date;
        public DateTime Date
        {
            get { return _date; }
            set { 
                _date = value;
                NotifyPropertyChanged("Date");
            }
        }


        private string _comment;
        public string Comment
        {
            get { return _comment; }
            set
            {
                _comment = value;
                NotifyPropertyChanged("Comment");
            }
        }

        private int _traderId;
        public int TraderId
        {
            get { return _traderId; }
            set
            {
                _traderId = value;
                NotifyPropertyChanged("TraderId");
            }
        }

        private Trader _trader;

        public Trader Trader
        {
            get { return _trader; }
            set { _trader = value;

            TraderData t = new TraderData();
            this.Trader = t.Select(value);
            }
        }


        private string _insertType;
        /// <summary>
        /// A - Auto, M - Manual, V - Verified
        /// </summary>
        public string InsertType
        {
            get { return _insertType; }
            set { _insertType = value;
            NotifyPropertyChanged("InsertType");
            }
        }



        public event PropertyChangedEventHandler PropertyChanged;
        // This method is called by the Set accessor of each property.
        // The CallerMemberName attribute that is applied to the optional propertyName
        // parameter causes the property name of the caller to be substituted as an argument.
        //private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        private void NotifyPropertyChanged(String propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

物品基础CLASS

using System.ComponentModel;

namespace PFT.Base
{
    public class Item : INotifyPropertyChanged
    {
        private int _id;

        public int Id
        {
            get { return _id; }
            set { _id = value;
            NotifyPropertyChanged("Id");
            }
        }



        private string _name;

        public string Name
        {
            get { return _name; }
            set { _name = value;
            NotifyPropertyChanged("Name");
            }
        }


        private string _description;

        public string Description
        {
            get { return _description; }
            set { _description = value;
            NotifyPropertyChanged("Description");
            }
        }


        private float _defaultPrice;

        public float DefaultPrice
        {
            get { return _defaultPrice; }
            set { _defaultPrice = value;
            NotifyPropertyChanged("DefaultPrice");
            }
        }

        private bool _isIncome;

        public bool IsIncome
        {
            get { return _isIncome; }
            set { _isIncome = value;
            NotifyPropertyChanged("IsIncome");
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(string propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

    }
}

使用视图模型执行此操作的方式是给 Transaction 一个 Supplier 属性 和一个 Item 属性。这些属性将引用它们自己集合中的实际 ItemSupplier 对象。如果关系是每个事务一个 ItemID 和一个 SupplierID,则对象等效。如果交易可以是具有相同交易 ID 和不同供应商或商品 ID 的多个记录,则 Transaction 需要 集合 of ItemSupplier。我们也可以在 WPF 中做到这一点,但它比下面的简单示例需要更多的尖括号。

您可以在从数据库中获取项目时进行设置(无论您是这样做的),或者 Entity Framework 可以为您完成。

显示项目名称的真实简单列表框:添加 DisplayMemberPath.

<ListBox 
    ItemsSource="{Binding Transactions}" 
    SelectedItem="{Binding CurrentTransaction}"
    DisplayMemberPath="Item.Name"
    />

更复杂:

<ListBox 
    ItemsSource="{Binding Transactions}" 
    SelectedItem="{Binding CurrentTransaction}"
    >
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBlock>
                <Run Text="{Binding Item.Name, Mode=OneWay}" />
                <Run Text=" - " />
                <Run Text="{Binding Supplier.Name, Mode=OneWay}" />
                <Run Text=" " />
                <Run Text="{Binding Price, Mode=OneWay, StringFormat=c}" />
            </TextBlock>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

您还可以查看像 ListViewDataGrid 这样的列控件。

稍微跑题了,零代码隐藏有点极端。这是最后的手段,而不是第三条铁路。最少的代码隐藏是一个合理的通用原则。不要疯狂地试图避免它;这是有原因的。

更新

public class Transaction : INotifyPropertyChanged
{
    //  ... stuff ...

    public Item Item
    {
        get { return _item; }
        set { 
            _item = value; 
            //  If this property is ever set outside the Transaction 
            //  constructor, you ABSOLUTELY MUST raise PropertyChanged here. 
            //  Otherwise, make the setter private. But just raise the event.

            //  This has nothing whatsoever to do with when or whether the Item 
            //  class raises PropertyChanged, because this is not a property of the 
            //  Item class. This is a property of Transaction. 

            NotifyPropertyChanged("Item");
        }
    }

    //  ... more stuff ...