Event trigger issue when parsing xaml using MVVMLight in my WP7 app - c#

I am a bit of a novice when it comes to MVVM and C# in general, but I do not understand why I am getting the following xaml parse exception: AG_E_PARSER_BAD_TYPE
The exception occurs when attempting to parse my event trigger:
<applicationspace:AnViewBase
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:c="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WP7">
...and inside my grid:
<Button Name="LoginButton"
Content="Login"
Height="72"
HorizontalAlignment="Left"
Margin="150,229,0,0"
VerticalAlignment="Top"
Width="160">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<c:EventToCommand Command="{Binding LoginCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
The exception occurs at the i:EventTrigger EventName="Click" line.
Does anyone have any insight as to why this is happening? I have seen this used before, and am simply too inexperienced to discern why it isn't working for me.
I am obliged for any help, and thank you for your time.

I did not resolve this issue, but created a work around... I thought it might be helpful to some, so here it is:
I extended the button class by adding a command property to my new "BindableButton"
public class BindableButton : Button
{
public BindableButton()
{
Click += (sender, e) =>
{
if (Command != null && Command.CanExecute(CommandParameter))
Command.Execute(CommandParameter);
};
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(BindableButton), new PropertyMetadata(null, CommandChanged));
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public static DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(BindableButton), new PropertyMetadata(null));
private static void CommandChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
BindableButton button = source as BindableButton;
button.RegisterCommand((ICommand)e.OldValue, (ICommand)e.NewValue);
}
private void RegisterCommand(ICommand oldCommand, ICommand newCommand)
{
if (oldCommand != null)
oldCommand.CanExecuteChanged -= HandleCanExecuteChanged;
if (newCommand != null)
newCommand.CanExecuteChanged += HandleCanExecuteChanged;
HandleCanExecuteChanged(newCommand, EventArgs.Empty);
}
// Disable button if the command cannot execute
private void HandleCanExecuteChanged(object sender, EventArgs e)
{
if (Command != null)
IsEnabled = Command.CanExecute(CommandParameter);
}
}
Following this, I just bind a command in my xaml:
<b:BindableButton x:Name="LoginButton" Command="{Binding LoginCommand}"></b:BindableButton>

Related

UserControl in WinUI 3: how to set the 'source' property of an image and the 'click' event of a button?

I'm building a UserControl that should display a button with an image and text.
I access that UserControl in the App like this:
<local:ButtonWithImage
ButtonClick="Button1_Click"
ButtonImage="Assets/Clipboard 4.png"
ButtonText="Clipboard History"
ButtonWidth="200" />
Out of the 4 properties displayed in the code above, two of them are working fine, which are ButtonText and ButtonWidth.
But the ButtonClick and ButtonImage properties are causing errors, which I'll explain next.
The UserControl code is this:
xaml:
<UserControl
x:Class="Launcher_WinUI3_Net_6.ButtonWithImage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:Launcher_WinUI3_Net_6"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<StackPanel>
<StackPanel Orientation="Horizontal">
<Button x:Name="button">
<StackPanel Orientation="Horizontal">
<Image x:Name="image"/>
<TextBlock x:Name="textBlock" />
</StackPanel>
</Button>
</StackPanel>
<TextBlock Height="1" />
</StackPanel>
</UserControl>
C#:
public sealed partial class ButtonWithImage : UserControl
{
public ButtonWithImage()
{
this.InitializeComponent();
}
public string ButtonText
{
get { return (string)GetValue(ButtonTextProperty); }
set { SetValue(ButtonTextProperty, value); }
}
public static readonly DependencyProperty
ButtonTextProperty =
DependencyProperty.Register("ButtonText",
typeof(string), typeof(ButtonWithImage),
new PropertyMetadata(string.Empty, ButtonTextValue));
private static void ButtonTextValue(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var buttonWithImage = d as ButtonWithImage;
var buttonWithImageProperty = buttonWithImage.FindName("textBlock") as TextBlock;
buttonWithImageProperty.Text = e.NewValue.ToString();
}
public string ButtonWidth
{
get { return (string)GetValue(ButtonWidthProperty); }
set { SetValue(ButtonWidthProperty, value); }
}
public static readonly DependencyProperty
ButtonWidthProperty =
DependencyProperty.Register("ButtonWidth",
typeof(string), typeof(ButtonWithImage),
new PropertyMetadata(string.Empty, ButtonWidthValue));
private static void ButtonWidthValue(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var buttonWithImage = d as ButtonWithImage;
var buttonWithImageProperty = buttonWithImage.FindName("button") as Button;
buttonWithImageProperty.Width = Convert.ToDouble(e.NewValue.ToString());
}
public string ButtonClick
{
get { return (string)GetValue(ButtonClickProperty); }
set { SetValue(ButtonClickProperty, value); }
}
public static readonly DependencyProperty
ButtonClickProperty =
DependencyProperty.Register("ButtonClick",
typeof(string), typeof(ButtonWithImage),
new PropertyMetadata(string.Empty, ButtonClickValue));
private static void ButtonClickValue(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var buttonWithImage = d as ButtonWithImage;
var buttonWithImageProperty = buttonWithImage.FindName("button") as Button;
buttonWithImageProperty.Click += e.NewValue.ToString();
}
public string ButtonImage
{
get { return (string)GetValue(ButtonImageProperty); }
set { SetValue(ButtonImageProperty, value); }
}
public static readonly DependencyProperty
ButtonImageProperty =
DependencyProperty.Register("ButtonImage",
typeof(string), typeof(ButtonWithImage),
new PropertyMetadata(string.Empty, ButtonImageValue));
private static void ButtonImageValue(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var buttonWithImage = d as ButtonWithImage;
var buttonWithImageProperty = buttonWithImage.FindName("image") as Image;
buttonWithImageProperty.Source = e.NewValue.ToString();
}
}
The code for the ButtonClick is generating this error: Cannot implicitly convert type 'string' to 'Microsoft.UI.Xaml.RoutedEventHandler'
And the code for the ButtonImage is generating this error: Cannot implicitly convert type 'string' to 'Microsoft.UI.Xaml.Media.ImageSource'
I don't have much experience with creating UserControls so I'm following some examples I've seen on the internet, but none of them address these two problems I'm facing.
========================================================
Update 1 based on answer from Andrew KeepCoding:
Thanks Andrew!!!
There is still an error going on: No overload for 'Button52_Click' matches delegate 'EventHandler'
UserControl in the App:
<local:ButtonWithImage
ButtonImage="Assets/Clipboard 4.png"
ButtonText="Clipboard History"
ButtonWidth="200"
Click="Button52_Click" />
Button52_Click signature:
private void Button52_Click(object sender, RoutedEventArgs e)
{
foo();
}
UserControl 'Click' event signature:
public event EventHandler? Click;
private void button_Click(object sender, RoutedEventArgs e)
{
Click?.Invoke(this, EventArgs.Empty);
}
The signatures are the same, even so the error No overload for 'Button52_Click' matches delegate 'EventHandler' is occurring
The error is occurring here, in 'case 41:':
case 40: // MainWindow.xaml line 1288
{
global::Microsoft.UI.Xaml.Controls.Button element40 = global::WinRT.CastExtensions.As<global::Microsoft.UI.Xaml.Controls.Button>(target);
((global::Microsoft.UI.Xaml.Controls.Button)element40).Click += this.Button51_Click;
}
break;
case 41: // MainWindow.xaml line 1199
{
global::Launcher_WinUI3_Net_6.ButtonWithImage element41 = global::WinRT.CastExtensions.As<global::Launcher_WinUI3_Net_6.ButtonWithImage>(target);
((global::Launcher_WinUI3_Net_6.ButtonWithImage)element41).Click += this.Button52_Click;
}
break;
========================================================
Update 2:
The Button52_Click signature should be:
private void Button52_Click(object sender, EventArgs e)
{
foo();
}
And not:
private void Button52_Click(object sender, RoutedEventArgs e)
{
foo();
}
Instead of typeof(string), you should use the actual type for your dependency properties.
For example, I'm using ImageSource for the ButtonImage in the code below:
<UserControl
x:Class="UserControls.ButtonWithImage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<StackPanel>
<StackPanel Orientation="Horizontal">
<Button
x:Name="button"
Click="button_Click">
<StackPanel Orientation="Horizontal">
<Image
x:Name="image"
Source="{x:Bind ButtonImage, Mode=OneWay}" />
<TextBlock
x:Name="textBlock"
Text="{x:Bind ButtonText, Mode=OneWay}" />
</StackPanel>
</Button>
</StackPanel>
<TextBlock Height="1" />
</StackPanel>
</UserControl>
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using System;
namespace UserControls;
public sealed partial class ButtonWithImage : UserControl
{
public static readonly DependencyProperty ButtonTextProperty = DependencyProperty.Register(
nameof(ButtonText),
typeof(string),
typeof(ButtonWithImage),
new PropertyMetadata(default));
public static readonly DependencyProperty ButtonImageProperty = DependencyProperty.Register(
nameof(ButtonImage),
typeof(ImageSource),
typeof(ButtonWithImage),
new PropertyMetadata(default));
public ButtonWithImage()
{
this.InitializeComponent();
}
public event EventHandler? Click;
public string ButtonText
{
get => (string)GetValue(ButtonTextProperty);
set => SetValue(ButtonTextProperty, value);
}
public ImageSource? ButtonImage
{
get => (ImageSource?)GetValue(ButtonImageProperty);
set => SetValue(ButtonImageProperty, value);
}
private void button_Click(object sender, RoutedEventArgs e)
{
Click?.Invoke(this, EventArgs.Empty);
}
}
And use it like this:
<local:ButtonWithImage
ButtonText="Text"
ButtonImage="Assets/StoreLogo.png"
Click="ButtonWithImage_Click" />
private void ButtonWithImage_Click(object sender, EventArgs e)
{
}
You should also consider a custom control derived from a Button. These videos might help.
UserControls
CustomControls

Bind KeyBinding Command to WPF DatePicker (MVVM)

I've been searching high and low for a way to bind the Return key to a DatePicker control in a MVVM way, but to no avail.
My current XAML Markup:
<DatePicker x:Name="DateFilter" SelectedDate="{Binding SearchDate, UpdateSourceTrigger=PropertyChanged}">
<DatePicker.InputBindings>
<KeyBinding Key="Return" Command="{Binding SearchCommand}"></KeyBinding>
</DatePicker.InputBindings>
</DatePicker>
The closest thing I found is rewriting the entire control template, but this both requires a lot of markup and screws up the original control appearance.
Right now, I've hacked a solution together by forcefully adding the DatePicker InputBindings to the underlying DatePickerTextBox from the form codebehind, but it's awful as it requires writing code behind and uses reflection:
Form CodeBehind (bound to the Loaded event of the view):
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
var fiTextBox = typeof(DatePicker)
.GetField("_textBox", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
if (fiTextBox?.GetValue(DateFilter) is System.Windows.Controls.Primitives.DatePickerTextBox textBox)
{
textBox.InputBindings.AddRange(DateFilter.InputBindings);
}
}
Is there a better way to do this?
(Note: I cannot bind the execution of the command to the change of the bound SearchDate property as the operation is quite expensive and I don't want it to fire every time the user picks a new date. However, I need the property to immediately refresh as the CanExecute of the command is also tied to said Date not being null.)
You could use a reusable attached behaviour:
public static class ReturnKeyBehavior
{
public static ICommand GetCommand(UIElement UIElement) =>
(ICommand)UIElement.GetValue(CommandProperty);
public static void SetCommand(UIElement UIElement, ICommand value) =>
UIElement.SetValue(CommandProperty, value);
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached(
"Command",
typeof(ICommand),
typeof(ReturnKeyBehavior),
new UIPropertyMetadata(null, OnCommandChanged));
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UIElement uie = (UIElement)d;
ICommand oldCommand = e.OldValue as ICommand;
if (oldCommand != null)
uie.RemoveHandler(UIElement.PreviewKeyDownEvent, (KeyEventHandler)OnMouseLeftButtonDown);
ICommand newCommand = e.NewValue as ICommand;
if (newCommand != null)
uie.AddHandler(UIElement.PreviewKeyDownEvent, (KeyEventHandler)OnMouseLeftButtonDown, true);
}
private static void OnMouseLeftButtonDown(object sender, KeyEventArgs e)
{
if(e.Key == Key.Enter)
{
UIElement uie = (UIElement)sender;
ICommand command = GetCommand(uie);
if (command != null)
command.Execute(null);
}
}
}
...that can be attached to any UIElement in XAML:
<DatePicker local:ReturnKeyBehavior.Command="{Binding ListViewItemMouseLeftButtonDownCommand}" />
Then you don't have to deal with any keys in the view model.
I'd probably use an interaction trigger, or whatever else your framework uses to convert events to commands, and then trap PreviewKeyDown:
<DatePicker x:Name="DateFilter" SelectedDate="{Binding SearchDate, UpdateSourceTrigger=PropertyChanged}"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd ="http://www.galasoft.ch/mvvmlight">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewKeyDown">
<cmd:EventToCommand Command="{Binding KeyDownCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</DatePicker>
And then in your view model:
private ICommand _KeyDownCommand;
public ICommand KeyDownCommand => this._KeyDownCommand ?? (this._KeyDownCommand = new RelayCommand<KeyEventArgs>(OnKeyDown));
private void OnKeyDown(KeyEventArgs args)
{
if (args.Key == Key.Return)
{
// do something
}
}

How to put a user control in a static layer ontop of all other controls?

I'm developing an autocomplete user control for WPF using XAML and C#.
I would like to have the pop-up for the suggestions to appear above all controls. Currently my pop up is a ListView . That causes problems since whenever I decide to show it the UI must find a place for it and to do so moves all the controls which are below it further down.
How can I avoid this? I assume I must put it in a layer which is above all of the other controls?
I have written "auto-complete" style controls before by using the WPF Popup control, combined with a textbox. If you use Popup it should appear, as you say, in a layer over the top of everything else. Just use Placement of Bottom to align it to the bottom of the textbox.
Here is an example that I wrote a while ago. Basically it is a text box which, as you type pops up a suggestions popup, and as you type more it refines the options down. You could fairly easily change it to support multi-word auto-complete style code editing situations if you wanted that:
XAML:
<Grid>
<TextBox x:Name="textBox"
Text="{Binding Text, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:IntelliSenseUserControl}}}"
KeyUp="textBox_KeyUp"/>
<Popup x:Name="popup"
Placement="Bottom"
PlacementTarget="{Binding ElementName=textBox}"
IsOpen="False"
Width="200"
Height="300">
<ListView x:Name="listView"
ItemsSource="{Binding FilteredItemsSource, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:IntelliSenseUserControl}}}"
SelectionChanged="ListView_Selected"/>
</Popup>
</Grid>
Code-behind:
public partial class IntelliSenseUserControl : UserControl, INotifyPropertyChanged
{
public IntelliSenseUserControl()
{
InitializeComponent();
DependencyPropertyDescriptor prop = DependencyPropertyDescriptor.FromProperty(ItemsSourceProperty, typeof(IntelliSenseUserControl));
prop.AddValueChanged(this, ItemsSourceChanged);
}
private void ItemsSourceChanged(object sender, EventArgs e)
{
FilteredItemsSource = new ListCollectionView((IList)ItemsSource);
FilteredItemsSource.Filter = (arg) => { return arg == null || string.IsNullOrEmpty(textBox.Text) || arg.ToString().Contains(textBox.Text.Trim()); };
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(IntelliSenseUserControl), new FrameworkPropertyMetadata(null) { BindsTwoWayByDefault = true });
public object ItemsSource
{
get { return (object)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(object), typeof(IntelliSenseUserControl), new PropertyMetadata(null));
#region Notified Property - FilteredItemsSource (ListCollectionView)
public ListCollectionView FilteredItemsSource
{
get { return filteredItemsSource; }
set { filteredItemsSource = value; RaisePropertyChanged("FilteredItemsSource"); }
}
private ListCollectionView filteredItemsSource;
#endregion
private void textBox_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Return || e.Key == Key.Enter)
{
popup.IsOpen = false;
}
else
{
popup.IsOpen = true;
FilteredItemsSource.Refresh();
}
}
private void UserControl_LostFocus(object sender, RoutedEventArgs e)
{
popup.IsOpen = false;
}
private void ListView_Selected(object sender, RoutedEventArgs e)
{
if (listView.SelectedItem != null)
{
Text = listView.SelectedItem.ToString().Trim();
}
}
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
If your Window's content container is a Grid, you can simply do something like
<ListBox Grid.RowSpawn="99" Grid.ColumnSpan="99"/>
to "simulate" an absolute position. You then just have to set its position with Margin, HorizontalAlignment and VerticalAlignment so it lays around the desired control.

MVVM handling the Drag events of MouseDragElementBehavior

This question tells me what to do in words, but I can't figure out how to write the code. :)
I want to do this:
<SomeUIElement>
<i:Interaction.Behaviors>
<ei:MouseDragElementBehavior ConstrainToParentBounds="True">
<i:Interaction.Triggers>
<i:EventTrigger EventName="DragFinished">
<i:InvokeCommandAction Command="{Binding SomeCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ei:MouseDragElementBehavior>
</i:Interaction.Behaviors>
</SomeUIElement>
But as the other question outlines, the EventTrigger doesn't work... I think it's because it wants to find the DragFinished event on the SomeUIElement instead of on the MouseDragElementBehavior. Is that correct?
So I think what I want to do is:
Write a behavior that inherits from MouseDragElementBehavior
Override the OnAttached method
Subscribe to the DragFinished event... but I can't figure out the code to do this bit.
Help please! :)
Here is what I implemented to solve your problem :
public class MouseDragCustomBehavior : MouseDragElementBehavior
{
public static DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(MouseDragCustomBehavior));
public static DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(MouseDragCustomBehavior));
protected override void OnAttached()
{
base.OnAttached();
if (!DesignerProperties.GetIsInDesignMode(this))
{
base.DragFinished += MouseDragCustomBehavior_DragFinished;
}
}
private void MouseDragCustomBehavior_DragFinished(object sender, MouseEventArgs e)
{
var command = this.Command;
var param = this.CommandParameter;
if (command != null && command.CanExecute(param))
{
command.Execute(param);
}
}
protected override void OnDetaching()
{
base.OnDetaching();
base.DragFinished -= MouseDragCustomBehavior_DragFinished;
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
}
And then the XAML to call it like this....
<Interactivity:Interaction.Behaviors>
<Controls:MouseDragCustomBehavior Command="{Binding DoCommand}" />
</Interactivity:Interaction.Behaviors>

Dependency Property in User Control works only on the first instance

I have several custom user controls in a window. They appear dynamically, like workspaces.
I need to add a dependency property on an itemscontrol to trigger a scrolldown when an item is being added to the bound observable collection to my itemscontrol, like so:
(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 of 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 instantiation of my user control, it works like a charm.
But when another user control of the same type is dynamically instantiated, the DependencyProperty is never attached anymore to my scrollviewer. Only the first instance will work correctly.
I know that dependency properties are static, but does that mean they can't work at the same time on several user control of the same type added to the window?
Update 02/03 : Here's how 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, it won't work.
But if I set the datacontext in a classic way like usercontrol.datacontext = new viewmodel(), it definitely work. But it's recommended to have a "shared" view, so how do we make dependency properties work with this "xaml" way of setting datacontext ?
Sorry, I couldn't reproduce your problem.
I started Visual C# 2010 Express, created a new 'WPF Application', added your XAML to a UserControl that I imaginatively titled UserControl1, and added your ItemsControlBehavior class. I then modified the MainWindow that VC# created for me as follows:
MainWindow.xaml (contents of <Window> element only):
<StackPanel Orientation="Vertical">
<Button Content="Add user control" Click="ButtonAddUserControl_Click" />
<Button Content="Add message" Click="ButtonAddMessage_Click" />
<StackPanel Orientation="Horizontal" x:Name="sp" Height="300" />
</StackPanel>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public ObservableCollection<string> Messages { get; private set; }
public MainWindow()
{
InitializeComponent();
Messages = new ObservableCollection<string>() { "1", "2", "3", "4" };
DataContext = this;
}
private void ButtonAddUserControl_Click(object sender, RoutedEventArgs e)
{
sp.Children.Add(new UserControl1());
}
private void ButtonAddMessage_Click(object sender, RoutedEventArgs e)
{
Messages.Add((Messages.Count + 1).ToString());
}
}
I made no modifications to the XAML in your UserControl, nor to your ItemsControlBehavior class.
I found that no matter how many user controls were added, their ScrollViewers all scrolled down to the bottom when I clicked the 'Add message' button.
If you're only seeing the scroll-to-the-bottom behaviour on one of your user controls, then there must be something that you're not telling us.

Categories