将 Binding 设置为其他 Binding 的源

发布于 2024-10-26 04:35:43 字数 1741 浏览 1 评论 0原文

我有一个 TabControl,我想要执行以下操作:

  1. 获取 TabControl.Items 中的第一个和最后一个 TabItem
  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 作为其他 BindingRelativeSourceSource。基本上,手头的解决方案是创建转换器,它将采用 TabControl.Items 并将其转换为最终值,但问题是我想对两者的 Margin 进行动画处理TabItem,所以我需要专门绑定到这些属性。如果我绑定到 TabControl.Items,则任何 TabItemMargin 都不会刷新 Border.Padding会改变的。那我该怎么办呢?

更新

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

I have a TabControl, and I want to do the following:

  1. Take first and last of the TabItems in TabControl.Items
  2. Get their Margins
  3. Supply those Thicknesses to the converter to convert these two structs into final value

Here is a related code showing what I am trying to do:

<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>

But I can't set Binding as RelativeSource or Source of other Binding. Basically the solution at hand is to create converter, which would take TabControl.Items and convert it to the final value, but the problem is I want to animate Margins of both TabItems, so I need to bind specifically to these properties. If I would bind to TabControl.Items, the Border.Padding would not get refreshed if Margin of any TabItem would change. So what should I do?

Update

Ok, so one of the possible solutions is to hook into TabItem.Loaded event, and then use DependencyPropertyDescriptor to hook Changed event on appropriate properties, then hook all items in TabItem.Items collection, hook any new items and automatically unhook all old items, and hook like million other stuff. But this is quite complicated and it's like 400 LOC. Isn't there anything simpler? Preferably in pure XAML.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

他不在意 2024-11-02 04:35:43

不幸的是,这个修改后的响应并不像我想要的那么优雅,但它应该有效。

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

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

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>

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

Unfortunately this modified response is not as elegant as I'd like, but it should work.

Basically, use another dummy control as a relay for the binding. Specifically, this scenario arises when you want to pull the first and last of a collection. You can't just use a converter to grab the first/last and the property all at once, because if you change a property on the first or last item, the converter won't pick up the change. So you have to do something that melds in with dependency properties - almost like some sort of a second order dependency property would be nice.

Anyways, here's some code:

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); }
        }
    }
}

And an actual use case:

<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>

So the idea here would be to grab the first and last item using the relay control and then bind to there margins.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文