I'm having problems passing the TextChanged
event of the TextBox
part of a ComboBox
to the associated view model.
As expected:
TextChanged
the OnTextChanged()
method in the code behind is called.KeyUp
the OnKeyUp()
method in the view model is called.However:
TextChanged
the OnTextChanged()
method in the view model is not called.Why is it not called and how can I fix it?
<UserControl x:Class="AutoComplete.Views.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:cal="http://www.caliburnproject.org"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
>
<UserControl.Resources>
<FrameworkElement x:Key="ProxyElement" DataContext="{Binding}"/>
</UserControl.Resources>
<Grid>
<ContentControl Visibility="Collapsed" Content="{StaticResource ProxyElement}"/>
<DataGrid ItemsSource="{Binding Rows}" AutoGenerateColumns="False" CanUserAddRows="True" CanUserDeleteRows="True">
<DataGrid.Columns>
<DataGridComboBoxColumn Header="Code">
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<EventSetter Event="TextBoxBase.TextChanged" Handler="OnTextChanged"/>
<Setter Property="cal:Action.TargetWithoutContext" Value="{Binding DataContext, Source={StaticResource ProxyElement}}"/>
<Setter Property="cal:Message.Attach" Value="[Event TextBoxBase.TextChanged] = [Action OnTextChanged($source, $dataContext)]; [Event KeyUp] = [Action OnKeyUp()]"/>
<Setter Property="IsEditable" Value="True"/>
<Setter Property="ItemsSource" Value="{Binding DataContext.Suggestions, Source={StaticResource ProxyElement}}"/>
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</UserControl>
This doesn't work because Caliburn Micro by default uses the EventTrigger
class for Message.Attach
. According to this post EventTrigger
uses reflection to find the event using the EventName
property which fails since the ComboBox
does not expose an event called TextBoxBase.TextChanged
. Neither does it expose a TextChanged
event, it's the TextChanged
bubbling up from the TextBox
component of the ComboBox
that is supposed to be caught.
The above post also provides an adaptable solution. First a new RoutedEventTrigger
class is created:
public class RoutedEventTrigger : EventTriggerBase<DependencyObject>
{
public RoutedEvent RoutedEvent { get; set; }
protected override void OnAttached()
{
var element = (AssociatedObject as Behavior as IAttachedObject)?.AssociatedObject as UIElement
?? AssociatedObject as UIElement;
element?.AddHandler(RoutedEvent, new RoutedEventHandler(OnRoutedEvent));
}
void OnRoutedEvent(object sender, RoutedEventArgs args)
{
OnEvent(args);
}
protected override string GetEventName()
{
return RoutedEvent.Name;
}
}
Then Caliburn Micro is configured to use RoutedEventTrigger instead of EventTrigger if possible:
public class Bootstrapper : BootstrapperBase
{
public Bootstrapper()
{
Initialize();
}
private RoutedEventTrigger CreateRoutedEventTrigger(DependencyObject target, string routedEvent)
{
var routedEvents = EventManager.GetRoutedEvents().ToDictionary(r => $"{r.OwnerType.Name}.{r.Name}");
if (routedEvents.ContainsKey(routedEvent))
{
var trigger = new RoutedEventTrigger
{
RoutedEvent = routedEvents[routedEvent]
};
trigger.Attach(target);
return trigger;
}
return null;
}
protected override void OnStartup(object sender, StartupEventArgs args)
{
var baseCreateTrigger = Parser.CreateTrigger;
Parser.CreateTrigger = (target, triggerText) =>
{
var baseTrigger = baseCreateTrigger(target, triggerText);
var baseEventTrigger = baseTrigger as EventTrigger;
return CreateRoutedEventTrigger(target, baseEventTrigger?.EventName ?? "") ?? baseTrigger;
};
...
}
...
}
After this setup bubbling RoutedEvents can be used.