【问题标题】:Add a Property to Entity Class in ViewModel在 ViewModel 中向实体类添加属性
【发布时间】:2023-03-14 20:11:02
【问题描述】:

我有一个 ProfileUser EntityCollection 的配置文件。在这个类中,我有一个 Profile_ID 和一个 Profile 关系和一个 User_ID 但没有 USer 关系,因为 User 在另一个数据库中。

在我想通过 User.Username 访问的 Datagrid 中

我试过了,但是ofc不起作用...

public EntityCollection<ProfileUser> ProfileUsers
    {
        get
        {
            if (profile != null) return profile.ProfileUser;
            else return null;
        }
        set
        {
            profile.ProfileUser = value;
        }
    }

这里是我的自定义类

public class ProfileUserExtended : ProfileUser
{
    public Operator User
    {
        get
        {
            return OperatorManager.GetByGuId(this.User_ID);
        }
    }
}

当然不能通过基类来构造派生类。但我需要这个 Operator 成为我绑定到的集合的一部分...

希望你能理解我的问题并能提供帮助。

编辑: 这个转换器为我解决了这个问题:

public class OperatorConverter:IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        try
        {
            if (!(value is Guid)) return null;
            if (!(parameter is string)) return null;

            var guid = (Guid)value;
            var par = (string)parameter;

            var op = OperatorManager.GetByGuId(guid);
            if (op == null) return null;

            var prop = op.GetType().GetProperty(par);
            if (prop == null) return null;

            return prop.GetValue(op, null);
        }
        catch (Exception e)
        {
            throw (e);
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new System.NotImplementedException();

    }
}

在 XAML 中:

<DataGridTextColumn Header="Name" Binding="{Binding Path=User_ID,Converter={StaticResource ResourceKey=operatorConverter},ConverterParameter='Name'}" IsReadOnly="True" />

【问题讨论】:

    标签: c# inheritance properties overloading derived-class


    【解决方案1】:

    这里有三种方法可以实现与您需要做的类似的多个绑定。

    1) 一个包装类:

    public class ProfileUserWrapper : DependencyObject
    {
        public ProfileUserWrapper(ProfileUser thebrain) { TheUser = thebrain; }
    
        public ProfileUser TheUser { get; private set; }
    
        public Operator User { get { if (_user != null)return _user; return _user = OperatorManager.GetByGuId(TheUser.User_ID); } }
        private Operator _user = null;
    }
    

    现在,您可以公开IEnumerable&lt;ProfileUserWrapper&gt;,而不是public EntityCollection&lt;ProfileUser&gt; ProfileUsers

    public EntityCollection<ProfileUser> ProfileUsers // your original code
    {
        get{ if (profile != null) return profile.ProfileUser; else return null;}
        set { profile.ProfileUser = value; }
    }
    
    public IEnumerable<ProfileUserWrapper> ProfileUsers2
    {
        get { return ProfileUsers.Select(user => new ProfileUserWrapper(user));
    }
    

    然后绑定到 ProfileUsers2,您的一些绑定应该从“Address”更改为“TheUser.Address”,但这几乎肯定会起作用。

    2) 第二个,智能转换器,例如:

    public class OperatorPicker : IValueConverter
    {
        public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            var pu = value as ProfileUser;
            if (pu != null)
                return OperatorManager.GetByGuId(pu.User_ID);
            return null;
        }
    
        public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new System.NotImplementedException();
        }
    }
    

    写起来简直再简单不过了。现在您可以在 XAML 绑定中使用转换器:

    <Window.Resources>
        <myextra:OperatorPicker x:Key="conv1" />
    </Window.Resources>
    
    <Grid>
        <ListBox x:Name="lbxFirst" ItemsSource="{Binding ProfileUsers}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Margin="5" Text="{Binding User_ID}" />
                        <TextBlock Margin="5" Text="{Binding Login}" />
                        <TextBlock Margin="5" Text="{Binding Address}" />
                        <TextBlock Margin="5" Text="{Binding Path=., Converter={StaticResource conv1}}" />
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
    

    但是通过这种方式,您将获得 Operator 自己的对象。允许您绑定到以这种方式返回的运算符的属性将非常困难,因为 Binding 的路径已经固定在“。”,并且您无法更改它,因为必须向 Converter 传递 ProfileUser 实例。

    3) 第三,最复杂但完全没有任何包装器的工作是基于一个附加属性、转换器和两个绑定,但您也可以在两个附加属性和一个更改回调上完成。我更喜欢前一种方式,所以这里是:

    public class DummyClass : DependencyObject
    {
        public static readonly DependencyProperty TheOperatorProperty = DependencyProperty.RegisterAttached(
            "TheOperator", typeof(Operator), typeof(DummyClass),
            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender)
        );
    
        public static Operator GetTheOperator(DependencyObject elem) { return (Operator)elem.GetValue(TheOperatorProperty); }
        public static void SetTheOperator(DependencyObject elem, Operator value) { elem.SetValue(TheOperatorProperty, value); }
    }
    
        ... xmlns:myextra="clr-namespace:...." ....
    
    <Window.Resources>
        <myextra:OperatorPicker x:Key="conv1" />
    </Window.Resources>
    
    <Grid>
        <ListBox x:Name="lbxFirst" ItemsSource="{Binding ProfileUsers}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel x:Name="aparent" Orientation="Horizontal"
                                myextra:DummyClass.TheOperator="{Binding Path=., Converter={StaticResource conv1}}">
                        <TextBlock Margin="5" Text="{Binding User_ID}" />
                        <TextBlock Margin="5" Text="{Binding Login}" />
                        <TextBlock Margin="5" Text="{Binding Address}" />
                        <TextBlock Margin="5"
                                   Text="{Binding Path=(myextra:DummyClass.TheOperator).OperatorCodename, ElementName=aparent}" />
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
    

    请注意,在 StackPanel 级别有一个新的绑定。这个可以放在数据模板中任何地方,甚至是文本框本身。请注意它如何通过转换器将 ProfileUser 转换为 Operator。 Path=. 不是必需的,但我添加了它以便显示绑定的确切含义。请注意最后一个文本框绑定是如何指定的:它是与附加属性的绑定(通过元素名称),而不是与原始数据的绑定!

    这一次我测试了所有东西,它在我这边工作,在 ProfileUser 上没有 DependencyObject 继承。 DummyClass 继承满足了它。如果你试试这个,请像往常一样给我留言:)

    【讨论】:

    • 哇,你真是太棒了!我明天试试这个,然后给你一个反馈!
    • 嘿,你的解决方案 2 对我的情况来说是最好的,我在第一篇文章中添加了最终代码。非常感谢您花时间帮助我!
    • 没问题:) 但是,如果您已经拥有转换器,我真的鼓励您也尝试第三种。这是因为如果您想绑定到Operator 类的许多子属性,那么使用转换器将很难(或代码丑陋)。如果您想进行 2 路绑定,情况会更糟。 #3 只是多一点代码,但在 Bindings 如何工作方面更“干净” :) 无论如何,使用适合您当前需求的任何内容。再见。
    • 转换器在我看来是完美解决方案的唯一原因是单向绑定。我只需要显示这些附加信息,而不需要保存它们。而且我也不需要使用或更改 ProfileUser 类,这将是不可能的或至少有这么多工作......解决方案 3 我也会尝试,但我目前对解决方案 2 非常满意。
    【解决方案2】:

    编辑:这是一个很好的解决方案,但事实证明它需要目标数据对象继承自 DependencyObject,但事实并非如此。在这里,数据对象可能继承自 EntityObject 并且无法更改,因为它是源 DAO 对象:)

    话虽如此,让我们来玩一下附加属性:


    您有时可以使用Attached Properties 轻松解决它。绑定引擎不仅可以绑定到普通属性或依赖属性。实际上它可以绑定 5 个或更多的东西,其中最简单的是 Att.Props。

    附加属性是在某个随机类(称为 ABC)中定义的“虚拟”或更确切地说是“假”属性(我们将其命名为“IsZonk”),但以特殊方式注册,以便绑定引擎对其进行处理就好像它出现在您指定的目标类上一样(比如说 XYZ)。任何通过绑定访问 XYZ 上的 IsZonk 的尝试都会导致绑定引擎将请求退回到 ABC 类。此外,在 ABC 类中生成的方法调用将被赋予发出请求的确切 XYZ 对象。

    通过这种方式,您可以使用新数据甚至新功能轻松扩展现有对象,甚至无需修改它们。很像 C# 3.5 版本中添加的“静态扩展方法”。

    namespace Whatever {
    
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
    
            lbxFirst.DataContext = new MyModel();
        }
    }
    
    public class MyModel
    {
        public IEnumerable<ProfileUser> ProfileUsers
        {
            get
            {
                var tmp = new[]
                {
                    new ProfileUser{ User_ID = "001", Login = "Adelle", Address = "123 Shiny Street" },
                    new ProfileUser{ User_ID = "002", Login = "Beatrice", Address = "456 Sleepy Hill" },
                    new ProfileUser{ User_ID = "003", Login = "Celine", Address = "789 Rover Dome" },
                };
    
                tmp[0].SetValue(ProfileUserExtras.UserProperty, new Operator { RelatedUser = tmp[0], OperatorCodename = "Birdie", PermissionLevel = 111 });
                tmp[1].SetValue(ProfileUserExtras.UserProperty, new Operator { RelatedUser = tmp[1], OperatorCodename = "Twin", PermissionLevel = 222 });
                tmp[2].SetValue(ProfileUserExtras.UserProperty, new Operator { RelatedUser = tmp[2], OperatorCodename = "Trident", PermissionLevel = 333 });
    
                return tmp;
            }
        }
    }
    
    public class ProfileUser : DependencyObject
    {
        public string User_ID { get; set; }
        public string Login { get; set; }
        public string Address { get; set; }
        //- Operator User {get{}} -- does NOT exist here
    }
    
    public class Operator
    {
        public ProfileUser RelatedUser { get; set; }
        public string OperatorCodename { get; set; }
        public int PermissionLevel { get; set; }
    }
    
    public static class ProfileUserExtras
    {
        public static readonly DependencyProperty UserProperty = DependencyProperty.RegisterAttached(
            "User",             // the name of the property
            typeof(Operator),   // property type
            typeof(ProfileUserExtras), // the TARGET type that will have the property attached to it
    
            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender) // whatever meta you like
        );
    }
    
    }
    

    最后是在 XAML 中使用它:

    .... xmlns:myextra="clr-namespace:Whatever" ....
    
    <ListBox x:Name="lbxFirst" ItemsSource="{Binding ProfileUsers}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Margin="5" Text="{Binding User_ID}" />
                    <TextBlock Margin="5" Text="{Binding Login}" />
                    <TextBlock Margin="5" Text="{Binding Address}" />
                    <TextBlock Margin="5" Text="{Binding Path=(myextra:ProfileUserExtras.User).OperatorCodename}" />
                    <TextBlock Margin="5" Text="{Binding Path=(myextra:ProfileUserExtras.User).PermissionLevel}" />
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
    

    请注意绑定中附加属性的表示法。它必须用括号括起来,而且几乎总是需要一个命名空间前缀,否则绑定引擎会在其他地方寻找其他东西。通过添加括号,您表明此属性是“附加属性”而不是普通属性。

    【讨论】:

    • 嘿,感谢您的快速回复,我可以想象这应该是如何工作的。我期待你的代码示例,因为我不知道如何实现这个;)
    • 我刚刚添加了示例。这也是binding to attached properties 上的一篇文章,但请注意,他从属性返回一个简单的值,因此绑定表达式中的括号后没有点属性。本文介绍了一些内容,但我鼓励您进行搜索和实验。这是一个非常有趣的话题!尤其是您有时可能会偶然发现的“附加行为”:)
    • 唯一困扰我的是 ProfileData 是否必须是 DependencyObject 才能允许“附加”。对不起,我真的不记得了,所以所有这些可能结果对 EntityObjects 不可用。在这种情况下,您必须将数据对象包装在一些简单的“视图”类中,或者您必须努力使用类型模型和属性描述符。让我知道你是否按照附加的方式成功了!
    • 嘿,为你解答!我尝试将用户绑定为这样的附加属性:Binding="{Binding (balls:AttachedProperties.User).UserName}",但我收到绑定错误:BindingExpression path error: '(balls:AttachedProperties.User)' property not found on 'object' ''ProfileUser' (HashCode=12444132)'. BindingExpression:Path=(balls:AttachedProperties.User).UserName; DataItem='ProfileUser'。您知道解决方案吗? Binding DataContext是ProfileUser,我把operator类型的Dependency Property注册到了ProfileUser,但是它连getter都进不去。
    • 嘿,我发现添加 Path= 可以解决这个问题,但现在我收到了消息,即 User Property is not found on AttachedProperties。我的意思是……当然,因为我是用ProfileUser注册的,所以我需要绑定Path=User.UserName来代替?
    猜你喜欢
    • 1970-01-01
    • 2018-07-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多