【问题标题】:Attached property attached only on the first user control instance仅附加在第一个用户控件实例上的附加属性
【发布时间】:2012-02-06 11:27:38
【问题描述】:

按照 mvvm 工作区(客户视图)上的 Josh Smith 示例,我有一个主窗口和一个主窗口视图模型,其中包含“ChatTabViewModel”的 ObservableCollection:

internal class FriendsListViewModel : ObservableObject
{
    #region bound properties
    private ICollectionView viewfriends;
    private ObservableCollection<ChatTabViewModel> _chatTab; 
    ...
    #endregion
}

我在 xaml 中有一个专门用于此集合的区域:

<ContentControl Grid.Column="0" Grid.Row="0" Grid.RowSpan="2" Content="{Binding Path=ChatTabs}" ContentTemplate="{StaticResource ChatTabsTemplate}" />

在我的资源字典中:

<DataTemplate DataType="{x:Type vm:ChatTabViewModel}">
    <View:ChatTabView />
</DataTemplate>

<DataTemplate x:Key="ClosableTabItemTemplate">
    <DockPanel>
      <Button
        Command="{Binding Path=CloseCommand}"
        Content="X"
        Cursor="Hand"
        DockPanel.Dock="Right"
        Focusable="False"
        FontFamily="Courier"
        FontSize="9"
        FontWeight="Bold"
        Margin="0,1,0,0"
        Padding="0"
        VerticalContentAlignment="Bottom"
        Width="16" Height="16"
        />
      <ContentPresenter
        Content="{Binding Path=Caption, Mode=OneWay}"
        VerticalAlignment="Center">
      </ContentPresenter>
    </DockPanel>
</DataTemplate>

<DataTemplate x:Key="ChatTabsTemplate">
    <TabControl
      IsSynchronizedWithCurrentItem="True"
      ItemsSource="{Binding}"
      ItemTemplate="{StaticResource ClosableTabItemTemplate}"
      Margin="4"/>
</DataTemplate>

在用户事件中,我在我的集​​合中添加了一个新的 ChattabViewModel,并且与之相关的视图出现在主窗口中。

但是当我尝试在 ChattabView 的滚动条上添加附加属性时,该属性将仅附加在第一个 ChattabViewModel 实例上,其他选项卡不会绑定到附加属性。这是 ChattabView XAML:

 <ScrollViewer VerticalScrollBarVisibility="Auto" Grid.Row="0">
  <ItemsControl ItemsSource="{Binding Messages}" View:ItemsControlBehavior.ScrollOnNewItem="True">
  <ItemsControl.ItemTemplate>
    <DataTemplate>
        <TextBox IsReadOnly="True" TextWrapping="Wrap" Text="{Binding Path=DataContext, RelativeSource={RelativeSource Self}}" />
    </DataTemplate>
  </ItemsControl.ItemTemplate>    
</ItemsControl>
</ScrollViewer>

以及附加属性的代码:

namespace GtalkOntre.View
{
    /// <summary>
    /// Util class to scroll down when a new message is added.
    /// </summary>
    /// <remarks>attached property called ScrollOnNewItem that when set to true hooks into the INotifyCollectionChanged events of the itemscontrol items source and upon detecting a new item, scrolls the scrollbar to it.</remarks>
    public class ItemsControlBehavior
    {
        static Dictionary<ItemsControl, Capture> Associations = new Dictionary<ItemsControl, Capture>();

        public static bool GetScrollOnNewItem(DependencyObject obj)
        {
            return (bool)obj.GetValue(ScrollOnNewItemProperty);
        }

        public static void SetScrollOnNewItem(DependencyObject obj, bool value)
        {
            obj.SetValue(ScrollOnNewItemProperty, value);
        }          

        public static DependencyProperty ScrollOnNewItemProperty =
            DependencyProperty .RegisterAttached(
                "ScrollOnNewItem",
                typeof(bool),
                typeof(ItemsControlBehavior),
                new UIPropertyMetadata(false, OnScrollOnNewItemChanged));

        public static void OnScrollOnNewItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var mycontrol = d as ItemsControl;
            if (mycontrol == null) return;
            bool newValue = (bool)e.NewValue;
            if (newValue)
            {
                mycontrol.Loaded += MyControl_Loaded;
                mycontrol.Unloaded += MyControl_Unloaded;
            }
            else
            {
                mycontrol.Loaded -= MyControl_Loaded;
                mycontrol.Unloaded -= MyControl_Unloaded;
                if (Associations.ContainsKey(mycontrol))
                    Associations[mycontrol].Dispose();
            }
        }

        static void MyControl_Unloaded(object sender, RoutedEventArgs e)
        {
            var mycontrol = (ItemsControl)sender;
            Associations[mycontrol].Dispose();
            mycontrol.Unloaded -= MyControl_Unloaded;
        }

        static void MyControl_Loaded(object sender, RoutedEventArgs e)
        {
            var mycontrol = (ItemsControl)sender;
            var incc = mycontrol.Items as INotifyCollectionChanged;
            if (incc == null) return;
            mycontrol.Loaded -= MyControl_Loaded;
            Associations[mycontrol] = new Capture(mycontrol);
        }

        class Capture : IDisposable
        {
            public ItemsControl mycontrol { get; set; }
            public INotifyCollectionChanged incc { get; set; }

            public Capture(ItemsControl mycontrol)
            {
                this.mycontrol = mycontrol;
                incc = mycontrol.ItemsSource as INotifyCollectionChanged;
                incc.CollectionChanged +=incc_CollectionChanged;
            }

            void incc_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            {                
                if (e.Action == NotifyCollectionChangedAction.Add)
                {
                    ScrollViewer sv = mycontrol.Parent as ScrollViewer;
                    sv.ScrollToBottom();
                }
            }

            public void Dispose()
            {
                incc.CollectionChanged -= incc_CollectionChanged;
            }
        }
    }
}

那么为什么附加属性只绑定一次,在 chattabviewmodel 集合的第一次“chattabview”出现时?因此,仅在第一个 chattabviewmodel 上工作。 当我全部关闭它们时,附加属性将在 chattabviewmodel 的最后一个实例上解除绑定,并且当我添加新的第一个 chattabviewmodel 时,该属性将正确绑定。所以它只在 mainwindowviewmodel 的“chattabviewmodel”集合的第一个实例和最后一个实例上触发。

经过一周的搜索,我现在有点绝望了……

到目前为止,我的假设是:问题可能与我在字典资源中将视图设置为视图模型的方式有关。视图可能是共享的,并且只有第一个滚动条可能会做出反应。我试图在 DataTemplate 标记上添加一个x:Shared = false 属性,但它没有改变任何东西。

【问题讨论】:

    标签: c# wpf mvvm dependency-properties attached-properties


    【解决方案1】:

    您确定有不同的ChatTabView 实例被创建吗?

    我相信 WPF 的 TabControl 会重新使用现有的模板,如果它是相同的,而不是创建一个新模板,并且只是替换它后面的 DataContext

    因此它只会创建您的ChatTabView 的一个副本,并且切换选项卡会将ChatTabView 后面的DataContext 替换为集合中的不同项目。

    【讨论】:

      【解决方案2】:

      你没有给我们看ChatTabsTemplate,所以我只能假设它包含TabControl。如果是这样,那就解释了你所看到的行为。 TabControl 延迟加载其子选项卡项,因此只有当前视图将被初始化,因此将附加属性应用于它。但是,当您切换选项卡时,您应该会看到相同的附加属性触发。不是这样吗?

      至于你的预感,不太对。 DataTemplate 正在被共享,但 DataTemplate 用于创建其内容的不同实例,这些实例未被共享。

      【讨论】:

      • 是的,我向你展示了资源字典中的 ChatTabsTemplate,也许你错过了它( ) 好的选项卡控件,所以我知道它只会绑定附加属性一次。但是不,附加属性仅针对第一个打开的选项卡(绑定到 chattabviewmodel 的实例)触发,其他以下选项卡将不具有所需的行为:(
      • 好的,我尝试不使用 tabcontrol,每次将新视图模型添加到集合中时都会绑定附加属性,并且效果很好。我认为您是对的,这不是将视图设置为视图模型的方式,而是 tabcontrol 处理附加属性的方式,仅绑定一次。那么现在,我应该如何使附加属性与我的 tabcontrol 中的所有选项卡一起工作:/
      • @Anna:我认为你误解了一点(或者我可能误解了)。当您更改选项卡时,TabControl 将加载视图,并且您的附加属性应该作为结果执行。只是它没有立即执行,因为视图是延迟加载的。如有必要(我仍然不明白您为什么关心附加属性何时执行,只要它执行),您可以使用谷歌技术让 TabControl 始终加载其所有子项,而不是延迟加载它们。
      • 我理解,因为我尝试以更经典的方式绑定视图,如下所示:usercontrol.datacontext = viewmodel 我在调试模式下看到了 tabcontrol 的工作原理。实际上,当您更改选项卡时,它会加载相关视图并此视图绑定其附加属性。但是当我使用 tabcontrol + 在资源字典中设置视图的方式时,附加属性仅适用于第一个选项卡面板。我检查过,当您切换选项卡时,其他视图不会绑定到附加属性。使用 tabcontrol 和在字典资源中设置视图存在一个真正的问题。试试这个组合,你会看到的。
      • 好的,我发现了问题。问题在于 tabcontrol 以这种方式绑定视图和视图模型(如图所示的字典资源)。当你通过datatemplate绑定视图时,tabcontrol只会加载一次视图。所以用户控件只加载一次,因此附加属性只绑定在第一个实例上。我不知道这是故意的还是某种 wpf 错误。
      猜你喜欢
      • 2012-08-20
      • 1970-01-01
      • 1970-01-01
      • 2018-03-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多