Closed worldbeater closed 5 years ago
A temporary solution is to extract the DataTemplate
into a separate UserControl
, and then embed it into the DataTemplate
. Generally, this approach is a lot more MVVMish than using event handlers, so a rule of thumb is to always create user controls and view models for them when working with DataTemplate
s. If you need to bind to the DataContext
(view model) of a parent control, then a wise decision would be to pass a reference to the parent view model down to children view models.
For example, on the parent control side it could look like as follows:
<ListBox SelectionMode="Toggle" Items="{Binding Files}">
<ListBox.ItemTemplate>
<DataTemplate DataType="interfaces:IFileViewModel">
<views:FileView DataContext="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And on the child control side it would be:
<UserControl xmlns="https://github.com/avaloniaui"
x:Class="Camelotia.Presentation.Avalonia.Views.FileView"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
PointerReleased="OnPointerReleased">
<TextBlock Text="{Binding Name}" />
</UserControl>
With the following code-behind:
public sealed class FileView : ReactiveUserControl<IFileViewModel>
{
public FileView()
{
AvaloniaXamlLoader.Load(this);
this.WhenActivated(disposables =>
{
Observable // Using Observables and ReactiveUI is possible as well
.FromEventPattern<PointerReleasedEventHandler, PointerReleasedEventArgs>(
handler => PointerReleased += handler,
handler => PointerReleased -= handler)
.Subscribe(args => Console.WriteLine("It also works with observables!"))
.DisposeWith(disposables);
});
}
// This works fine and throws no exceptions!
public void OnPointerReleased(object sender, PointerReleasedEventArgs args)
{
Console.WriteLine("It works with events!")
}
}
Feels like this one is a good old issue! 😅
@kekekeks should this be already supported on master now the XAML compiler is merged? Just tried it and got a runtime exception:
System.ArgumentException
HResult=0x80070057
Message=Delegate to an instance method cannot have null 'this'.
Source=System.Private.CoreLib
StackTrace:
at System.MulticastDelegate.ThrowNullThisInDelegateToInstance()
at System.MulticastDelegate.CtorClosed(Object target, IntPtr methodPtr)
at ControlCatalog.Pages.ListBoxPage.XamlIlClosure_4e8d2b91-7fe5-409e-855a-35e26bb05209.Build(IServiceProvider ) in D:\projects\AvaloniaUI\Avalonia\samples\ControlCatalog\Pages/ListBoxPage.xaml:line 15
at Avalonia.Markup.Xaml.XamlIl.Runtime.XamlIlRuntimeHelpers.<>c__DisplayClass0_0.<DeferredTransformationFactoryV1>b__0(IServiceProvider sp) in D:\projects\AvaloniaUI\Avalonia\src\Markup\Avalonia.Markup.Xaml\XamlIl\Runtime\XamlIlRuntimeHelpers.cs:line 21
at Avalonia.Markup.Xaml.Templates.TemplateContent.Load(Object templateContent) in D:\projects\AvaloniaUI\Avalonia\src\Markup\Avalonia.Markup.Xaml\Templates\TemplateContent.cs:line 43
at Avalonia.Markup.Xaml.Templates.DataTemplate.Build(Object data) in D:\projects\AvaloniaUI\Avalonia\src\Markup\Avalonia.Markup.Xaml\Templates\DataTemplate.cs:line 35
at Avalonia.Controls.Presenters.ContentPresenter.CreateChild() in D:\projects\AvaloniaUI\Avalonia\src\Avalonia.Controls\Presenters\ContentPresenter.cs:line 327
at Avalonia.Controls.Presenters.ContentPresenter.UpdateChild() in D:\projects\AvaloniaUI\Avalonia\src\Avalonia.Controls\Presenters\ContentPresenter.cs:line 224
at Avalonia.Controls.Presenters.ContentPresenter.ApplyTemplate() in D:\projects\AvaloniaUI\Avalonia\src\Avalonia.Controls\Presenters\ContentPresenter.cs:line 205
at Avalonia.Controls.Mixins.ContentControlMixin.<>c__DisplayClass1_0`1.<Attach>g__TemplateApplied|1(Object s, RoutedEventArgs ev) in D:\projects\AvaloniaUI\Avalonia\src\Avalonia.Controls\Mixins\ContentControlMixin.cs:line 73
Will investigate
In v0.8.3, event handlers don't work even if there's no DataTemplates in XAML.
The project is created by Visual Studio, with Avalonia MVVM Application
template.
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:AvaloniaApplication1.ViewModels;assembly=AvaloniaApplication1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="AvaloniaApplication1.Views.MainWindow"
Icon="/Assets/avalonia-logo.ico"
Title="AvaloniaApplication1">
<Design.DataContext>
<vm:MainWindowViewModel/>
</Design.DataContext>
<StackPanel>
<TextBlock Text="{Binding Greeting}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Button HorizontalAlignment="Center" VerticalAlignment="Center" PointerReleased="TestOnPointerReleased">Test</Button>
</StackPanel>
</Window>
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace AvaloniaApplication1.Views {
public class MainWindow : Window {
public MainWindow() {
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
}
private void InitializeComponent() {
AvaloniaXamlLoader.Load(this);
}
public void TestOnPointerReleased(object sender) {
System.Console.WriteLine("asdf");
}
}
}
These code results in the runtime exception:
Portable.Xaml.XamlObjectWriterException
HResult=0x80131500
Message=Could not convert object 'TestOnPointerReleased' (of type System.String) to {clr-namespace:System;assembly=mscorlib}EventHandler({clr-namespace:Avalonia.Input;assembly=Avalonia.Input}PointerReleasedEventArgs): Referenced value method TestOnPointerReleased in type AvaloniaApplication1.Views.MainWindow indicated by event System.EventHandler`1[[Avalonia.Input.PointerReleasedEventArgs, Avalonia.Input, Version=0.8.0.0, Culture=neutral, PublicKeyToken=null]] was not found
Source=Avalonia.Markup.Xaml
StackTrace:
at Portable.Xaml.XamlObjectWriterInternal.GetCorrectlyTypedValue(XamlMember xm, XamlType xt, Object value, Boolean fallbackToString)
at Portable.Xaml.XamlObjectWriterInternal.StoreAppropriatelyTypedValue(ObjectState state, MemberAndValue ms, Object obj, Object keyObj)
at Portable.Xaml.XamlObjectWriterInternal.OnWriteValue(Object value)
at Portable.Xaml.XamlServices.Transform(XamlReader xamlReader, XamlWriter xamlWriter, Boolean closeWriter)
at Avalonia.Markup.Xaml.AvaloniaXamlLoader.LoadFromReader(XamlReader reader, AvaloniaXamlContext context, IAmbientProvider parentAmbientProvider)
at Avalonia.Markup.Xaml.AvaloniaXamlLoader.Load(Stream stream, Assembly localAssembly, Object rootInstance, Uri uri)
at Avalonia.Markup.Xaml.AvaloniaXamlLoader.Load(Type type, Object rootInstance)
at Avalonia.Markup.Xaml.AvaloniaXamlLoader.Load(Object obj)
at AvaloniaApplication1.Views.MainWindow.InitializeComponent() in C:\Users\Balthild\source\repos\AvaloniaApplication1\AvaloniaApplication1\Views\MainWindow.xaml.cs:line 15
at AvaloniaApplication1.Views.MainWindow..ctor() in C:\Users\Balthild\source\repos\AvaloniaApplication1\AvaloniaApplication1\Views\MainWindow.xaml.cs:line 8
at AvaloniaApplication1.Program.AppMain(Application app, String[] args) in C:\Users\Balthild\source\repos\AvaloniaApplication1\AvaloniaApplication1\Program.cs:line 24
at AvaloniaApplication1.Program.Main(String[] args) in C:\Users\Balthild\source\repos\AvaloniaApplication1\AvaloniaApplication1\Program.cs:line 12
XamlObjectWriterException: Referenced value method TestOnPointerReleased in type AvaloniaApplication1.Views.MainWindow indicated by event System.EventHandler`1[[Avalonia.Input.PointerReleasedEventArgs, Avalonia.Input, Version=0.8.0.0, Culture=neutral, PublicKeyToken=null]] was not found
@balthild it's fixed in the 0.9 previews - you can get the previews from NuGet if you check "Include prerelease": https://www.nuget.org/packages/Avalonia/0.9.0-preview4
@grokys Thanks!
While struggling to prevent the
ListBox
from handling right mouse button clicks which are intended to be used for showingContextMenu
s and not selecting anything in theListBox
, I discovered that event handlers won't work when referencing the handler method from a control declared inside aDataTemplate
.The following setup doesn't work
and
will result into
PotableXaml
exception:the new XamlIl compiler throws the following at runtime, much more descriptive btw:
The following setup works
and
will result into a working application triggering the
OnPointerReleased
event handler.Repro
The repro can be found here.