【问题标题】:Set Binding as other Binding's Source将 Binding 设置为其他 Binding 的 Source
【发布时间】:2011-03-22 19:46:24
【问题描述】:

我有一个TabControl,我想做以下事情:

  1. 取TabItems 中TabControl.Items 中的第一个和最后一个
  2. 获取他们的Margins
  3. 将那些Thicknesses 提供给转换器以将这两个结构转换为最终值

这是显示我正在尝试做的相关代码:

<Border.Padding>
    <MultiBinding Converter="{StaticResource MarginsToPaddingConverter}">
        <Binding Path="Margin">
            <Binding.Source>
                <Binding RelativeSource="{RelativeSource AncestorType=TabControl}" Path="Items" Converter="{StaticResource ItemCollectionToFirstItemConverter}" ConverterParameter="{x:Type TabItem}" />
            </Binding.Source>
        </Binding>
        <Binding Path="Margin">
            <Binding.Source>
                <Binding RelativeSource="{RelativeSource AncestorType=TabControl}" Path="Items" Converter="{StaticResource ItemCollectionToLastItemConverter}" ConverterParameter="{x:Type TabItem}" />
            </Binding.Source>
        </Binding>
    </MultiBinding>
</Border.Padding>

但我不能将Binding 设置为RelativeSource 或Source 或其他Binding。基本上手头的解决方案是创建转换器,它将采用TabControl.Items并将其转换为最终值,但问题是我想为TabItems 的Margins 设置动画,所以我需要专门绑定到这些属性。如果我绑定到TabControl.Items,如果Margin 的任何TabItem 发生变化,Border.Padding 将不会被刷新。那我该怎么办?

更新

好的,因此一种可能的解决方案是挂钩TabItem.Loaded 事件,然后使用DependencyPropertyDescriptor 在适当的属性上挂钩Changed 事件,然后挂钩TabItem.Items 集合中的所有项目,挂钩任何新项目并自动取消所有旧物品的挂钩,并像数百万其他东西一样挂钩。但这很复杂,就像 400 LOC。没有更简单的吗?最好是纯XAML。

【问题讨论】:

    标签: c# .net wpf xaml binding


    【解决方案1】:

    不幸的是,这个修改后的响应没有我想要的那么优雅,但它应该可以工作。

    基本上,使用另一个虚拟控件作为绑定的中继。具体来说,当您想要拉取集合的第一个和最后一个时,就会出现这种情况。您不能只使用转换器同时获取第一个/最后一个和属性,因为如果您更改第一个或最后一个项目的属性,转换器将不会获取更改。所以你必须做一些与依赖属性融合的事情——几乎就像某种二阶依赖属性会很好。

    无论如何,这里有一些代码:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows.Controls;
    using System.Windows;
    using System.Collections;
    
    namespace WpfApplication1
    {
        public class CollectionHelper : Control
        {
            public static DependencyProperty CollectionProperty = DependencyProperty.Register(
                "Collection",
                typeof(IEnumerable),
                typeof(CollectionHelper),
                new FrameworkPropertyMetadata(OnCollectionChanged));
    
            public IEnumerable Collection
            {
                get { return GetValue(CollectionProperty) as IEnumerable; }
                set { SetValue(CollectionProperty, value); }
            }
    
            private static void OnCollectionChanged(object rawSender, DependencyPropertyChangedEventArgs args)
            {
                CollectionHelper sender = (CollectionHelper)rawSender;
                IEnumerable value = args.NewValue as IEnumerable;
                if(value==null)
                {
                    sender.First = null;
                    sender.Last = null;
                    return;
                }
                bool isFirstSet = false;
                object lastItemTemp = null;
                foreach (var item in value)
                {
                    if (!isFirstSet)
                    {
                        sender.First = item;
                        isFirstSet = true;
                    }
                    lastItemTemp = item;
                }
                if (!isFirstSet)
                    sender.First = null;
                sender.Last = lastItemTemp;
            }
    
            public DependencyProperty FirstProperty = DependencyProperty.Register(
                "First",
                typeof(object),
                typeof(CollectionHelper));
    
            public object First
            {
                get { return GetValue(FirstProperty); }
                set { SetValue(FirstProperty, value); }
            }
    
            public DependencyProperty LastProperty = DependencyProperty.Register(
                "Last",
                typeof(object),
                typeof(CollectionHelper));
    
            public object Last
            {
                get { return GetValue(LastProperty); }
                set { SetValue(LastProperty, value); }
            }
        }
    }
    

    还有一个实际用例:

    <Window x:Class="WpfApplication1.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:Helper="clr-namespace:WpfApplication1"
            Title="MainWindow" Height="350" Width="525">
        <StackPanel>
            <Helper:CollectionHelper x:Name="Helper" Collection="{Binding SomeStrings}" />
            <TextBlock Text="{Binding ElementName=Helper, Path=First}" />
            <TextBlock Text="{Binding ElementName=Helper, Path=Last}" />
        </StackPanel>
    </Window>
    

    所以这里的想法是使用中继控件抓取第一个和最后一个项目,然后绑定到那里的边距。

    【讨论】:

    • 您在一个 Binding 上设置了两次 Path 属性 - 我很确定这不会起作用。
    • 你是对的。请参阅编辑后的回复。它不像我想要的那样优雅,但它会给你一个可重复使用的工具。
    • 你的意思是像&lt;TextBlock Margin="{Binding ElementName=Helper, Path=First.Margin}" /&gt;?当Helper.First 为空时,这不会抛出异常吗?
    • 这部分留给读者作为练习。 ;) 但说真的,你有多种选择。您可以设置默认返回值来代替 null,例如空控件或具有 Margin 的东西等。您甚至可以将其设为 dep。道具!
    • 是的,使用另一个控件作为中介可能是唯一可重用的解决方案。有点遗憾的是,WPF 对此没有直接支持。可以预料到这一点。 :-)
    猜你喜欢
    • 1970-01-01
    • 2017-07-09
    • 1970-01-01
    • 2013-10-22
    • 1970-01-01
    • 2013-08-12
    • 1970-01-01
    • 1970-01-01
    • 2022-10-15
    相关资源
    最近更新 更多