双向将“虚拟”字符串列表绑定到列

时间:2012-03-14 21:38:41

标签: c# .net wpf data-binding binding

我有一个字符串列表。

嗯,从概念上讲。它们存储在其他地方,但是我想提供一个像列表一样的对象(并提供任何必要的事件),以及我可以绑定的属性。

我想对此数据建立双向绑定,将其显示为 DataGrid 中的可修改列。我有以下问题:

  • 我不能进行双向绑定,因为绑定需要一个路径(即我在列中看起来不像{Binding}{Binding Path=.},必须是{{1}如果我做对了,那就可以修改了,听起来很合理。)
  • 我不完全知道代理集合对象在接口方面应该是什么样的({Binding Path=someField"} + IEnumerable会不够?)

是否有任何解决方案不涉及为集合中的每个字符串创建一个代理对象?你能建议一个有效的设计吗?


为了让讨论保持在轨道上,让我们假设我想要绑定到这样的东西:

INotifyCollectionChanged

这不完全是我的情况,但当我知道如何绑定到这个时,我想我将能够处理这样的任何情况。

我不得不在该Source之上提供一个适配器对象,包含任何必要的接口和事件,但我不希望每行数据都有一个适配器对象。

7 个答案:

答案 0 :(得分:1)

我发现你的方法有点奇怪。

DataGrids通常用于显示行。行包含属于一起的数据。 例如,您可以轻松地将行映射到某个类。这意味着数据网格中的列表示类中的属性。

您尝试做的恰恰相反,您试图获取列值而不是行值之间的关系。

拥有一个你可以将列绑定到的类的集合会不会更容易?

例如

class MyClass : INotifyPropertyChanged
{
    // Remember to actually implement INotifyPropertyChanged
    string Column;
}

如果你有一个MyClass的ObservableCollection,你可以将DataGrid绑定到这个集合。每当我称之为“列”的属性发生变化时,您都可以更新您的特殊列表。

你可以通过挂钩一些事件来做到这一点。通过实现INotifyPropertyChanged,如果直接更新“列”值,您的列将会更新。

答案 1 :(得分:1)

虽然为Source做一个适配器是相对清楚的,但是,不幸的是,第二个问题的核心('没有将每个字符串包装在一个小对象中)是.Net和WPF内置的冲突..

首先,WPF确实为您提供了许多注册数据的方法。回调,但没有办法注册提供值的回调。我的意思是,"设置"阶段只是可扩展的,不可拦截的,并且" get" - 什么都没有。 WPF只会保留并返回缓存后的数据。

第二件事是.Net中string是......不可变的。

现在,如果你直接提供一个字符串作为无路径绑定或作为任何控件的datacontext,那么你就是 screwed 。问题是,WPF实际上只传递绑定的实际值,而没有"来自"的信息。底层控件将简单地给出字符串实例,并且没有合理的修改方式,因为字符串不能自行更改。您甚至不会收到有关此类尝试的通知,就像使用只读属性一样。还有什么 - 如果你设法拦截这样的修改尝试,并且如果你产生了一个合适的新字符串,WPF将永远不会再问你新的值。要更新用户界面,您必须通过手动,强制WPF重新询问您,例如更改原始绑定,使其指向其他位置(到新值)或设置datacontext(到新实例) )。一些VisualTree扫描是可行的,因为每个人都改变了#t;回调为您提供了DependencyObjects(Controls!),因此您可以向上/向下扫描并篡改其属性。请记住该选项 - 我会在一分钟内参考此内容。

所以,一切都归结为这样一个事实:要获得正常的双向绑定,你不必拥有一条路径,你只需要#34;必须有一个可变的底层数据对象。如果你有一个不可变的 - 那么你必须使用绑定到一个包含不可变值的可变属性。

话虽如此,如果你想修改,你只需要包装一些字符串。

另一个问题是,如何做到这一点。有很多方法可以做到这一点。当然,你可以像Joe和Davio建议的那样简单地包装它们(注意Joe:那里也需要INotify),或者你可以尝试用附加属性和/或行为和/或转换器做一些XAML技巧来做到这一点您。这是完全可行的,例如参见my other post - 我已经在那里展示了如何注入虚拟财产"从其他地方完全提取数据(一个绑定+转换器动态执行包装,第二个绑定从附加包装器中提取值)。这样你就可以创建一个"内容"字符串上的属性,该属性可以简单地返回字符串本身,并且它完全可以双向绑定,没有例外。

但是..它不会起作用2-ish。

在绑定/行为/召集链的根源处,会有一个不可变的字符串。一旦你的智能自动包装绑定链发生了修改后的'回调您将收到一对旧/新值的通知。您将能够将值重新映射到新旧字符串。如果您完美地实现了所有内容,WPF将只使用新值。如果你在某个地方绊倒,那么你必须将新值人为地推回到UI(参见我要求你记住的选项)。所以,没关系。没有包装器,旧值可见,它是可更改的,您已获得新值,UI显示新值。存储怎么样?

在此期间,您已获得旧/新价值对。如果你分析它们,你将获得旧的/新的字符串。但是,如何更新旧的不可变字符串?不行。即使自动换行有效,即使UI工作正常,即使编辑似乎有效,你现在仍然站在真正的任务中:你调用了修改后的回调,你必须实际更新那个不可变的字符串。

首先,你需要你的来源。它是静态的吗?唷。真好运!所以肯定它是实例。在on-modified回调中,我们只获得了一个旧的+新字符串..如何获取Source实例?选项:

  • 扫描VisualTree并在datacontexts中搜索它并使用找到的任何内容..
  • 添加一些附加属性和绑定以绑定虚拟" Source"属性到每个字符串并从新值中读取该属性

干得好,但气味,但没有其他选择。

等等,还有更多:不仅需要旧/新值和Source的实例!你还需要ROW INDEX。 d'哦!如何从绑定数据中获取?选项:

  • 扫描VisualTree并搜索它(blaargh)......
  • 添加一些附加属性和绑定以绑定虚拟" RowIndex"财产到每一个(blaaergh)......

此时,虽然我发现所有这些似乎都可以实现并且实际上可能正常工作,但我真的认为将每个字符串包装成一个小的

public class LocalItem // + INotifyPropertyChanged
{
    public int Index { get; }
    public Source Source { get; }

    public string Content
    {
        get { Source...}
        set { Source... }
    }
}

只会更具可读性,优雅和实施。并且不易出错,因为更多的细节将是明确的,而不是一些WPF的绑定+附加魔法......

答案 2 :(得分:0)

我有一些代码用于将自定义对象列表绑定到DataContextMenu。您可以将其更改为使用字符串列表并将其绑定到您需要的内容

class SampleCode
{
    class Team
    {
        private string _TeamName = "";
        private int _TeamProperty1 = 0;
        ObservableCollection<Territory> _Territories = new ObservableCollection<Territory>();

        public Team(string tName)
        {
            this.TeamName = tName;
        }

        public ObservableCollection<Territory> Territories
        {
            get { return _Territories; }
            set { _Territories = value; }
        }

        public string TeamName
        {
            get { return _TeamName; }
            set { _TeamName = value; }
        }

        public int TeamProperty1
        {
            get { return _TeamProperty1; }
            set { _TeamProperty1 = value; }
        }

    }

    class Territory
    {
        private string _TerritoryName = "";
        Team _AssociatedTeam = null;

        public Territory(string tName, Team team)
        {
            this.TerritoryName = tName;
            this.AssociatedTeam = team;
        }

        public Team AssociatedTeam
        {
            get { return _AssociatedTeam; }
            set { _AssociatedTeam = value; }
        }

        public string TerritoryName
        {
            get { return _TerritoryName; }
            set { _TerritoryName = value; }
        }


        public void Method1()
        {
            //Do Some Work
        }
    }

    class MyApplication
    {
        ObservableCollection<Team> _Teams = new ObservableCollection<Team>();
        ContextMenu _TeritorySwitcher = new ContextMenu();
        public MyApplication()
        {

        }

        public void AddTeam()
        {
            _Teams.Add(new Team("1"));
            _Teams.Add(new Team("2"));
            _Teams.Add(new Team("3"));
            _Teams.Add(new Team("4"));

            foreach (Team t in _Teams)
            {
                t.Territories.Add(new Territory("1", t));
                t.Territories.Add(new Territory("2", t));
                t.Territories.Add(new Territory("3", t));
            }

            SetContextMenu();
        }

        private void SetContextMenu()
        {
            HierarchicalDataTemplate _hdtTerritories = new HierarchicalDataTemplate();
            _hdtTerritories.DataType = typeof(Territory);

            HierarchicalDataTemplate _hdtTeams = new HierarchicalDataTemplate();
            _hdtTeams.DataType = typeof(Team);

            FrameworkElementFactory _TeamFactory = new FrameworkElementFactory(typeof(TreeViewItem));
            _TeamFactory.Name = "txtTeamInfo";
            _TeamFactory.SetBinding(TreeViewItem.HeaderProperty, new Binding("TeamProperty1"));

            FrameworkElementFactory _TerritoryFactory = new FrameworkElementFactory(typeof(TreeViewItem));
            _TerritoryFactory.Name = "txtTerritoryInfo";
            _TerritoryFactory.SetBinding(TreeViewItem.HeaderProperty, new Binding("TerritoryProperty1"));


            _hdtTeams.ItemsSource = new Binding("Territories");

            _hdtTeams.VisualTree = _TeamFactory;
            _hdtTerritories.VisualTree = _TerritoryFactory;

            _hdtTeams.ItemTemplate = _hdtTerritories;

            _TeritorySwitcher.ItemTemplate = _hdtTeams;
            _TeritorySwitcher.ItemsSource = this._Teams;
        }
    }
}

答案 3 :(得分:0)

懒惰解决方案

ObservableCollection<string>派生,并从源中填充该集合。在派生类中,注册到集合更改事件并相应地更新源。将DataGrid列绑定到observable集合。

编写这个应该非常简单,但是在复制集合中的所有数据方面存在很大的缺点。

更有效的解决方案

创建适配器(如您所建议)并实施IList<string>INotifyCollectionChanged。让列表操作直接落到源上。将DataGrid列绑定到适配器。

这种方法需要一些繁琐的样板,但它是WPF控件和Source之间的薄层。

答案 4 :(得分:0)

这实际上取决于您如何实现UI。 Bea Stollnitz在http://bea.stollnitz.com/blog/?p=344为WPF DataGrid虚拟化ItemsSource做了一个很好的帖子。通过工作,我使用它来编辑和显示数据。

答案 5 :(得分:0)

最简单的方法是将字符串放在包装类中。

public class Wrapper
{
    public string Content{get;set;}
}

然后通过包装类使用字符串。这是列表项保持不变但内容发生了变化。 问题是当你没有这样做的时候就会删除一个旧的字符串,并创建一个新的字符串并且该集合会混淆。

答案 6 :(得分:-2)

ObservableCollection<string>开头。然后将可绑定控件的ItemsSource设置为ObservableCollection。