I have several user controls in a window. They look dynamic, like workspaces. I need to add a dependency property to a control element in order to cause scrolling when the element is added to the associated observable collection for my controls, for example: (UserControl)
<ScrollViewer VerticalScrollBarVisibility="Auto" > <ItemsControl Grid.Row="0" 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>
And the code for my dependency property:
public class ItemsControlBehavior { static readonly 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 readonly DependencyProperty ScrollOnNewItemProperty = DependencyProperty.RegisterAttached( "ScrollOnNewItem", typeof(bool), typeof(ItemsControl), 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 += new RoutedEventHandler(MyControl_Loaded); mycontrol.Unloaded += new RoutedEventHandler(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; } } }
During the first instance of my user control, it works like a charm. But when another user control of the same type is dynamically created, DependencyProperty is no longer bound to my scrollviewer. Only the first instance will work correctly. I know that dependency properties are static, but does this mean that they cannot work simultaneously with multiple user controls of the same type that are added to the window?
Update 02/03: Here, as I set the viewmodel to the view (not programmatically):
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:testDp.ViewModel" xmlns:View="clr-namespace:testDp.View"> <DataTemplate DataType="{x:Type vm:ChatTabViewModel}"> <View:ChatTabView /> </DataTemplate> </ResourceDictionary>
even with x: shared = false in the datatemplate tag, this will not work. But if I set the datacontext in the classic way, for example usercontrol.datacontext = new viewmodel (), it definitely works. But he recommended having a “general” look, so how can we get the dependency properties to work with this “xaml” way to set the datacontext?