Xamarin Formsのマウスイベントが少ない話

この文章はローカルで書きためておいたので現状すでに乖離している可能性があります。

ここ最近xamarinを使用しているのだけどプラットフォーム互換が障害になっているのかマウス/タッチイベント系がなくて困る。MouseDown/Upとかそういうの。ないものはないので仕方がないわけで自作しようとかそんな話。

ということでBoxViewを継承して下記みたいなViewを作る。UWPをサポートする都合上Pointerという一番抽象度が高いかなとか思うUWPの名前にした。対応するレンダラはそのまま素直に作ればよいので特に問題は出ないと思う。PointerDown/Upイベントがないとかは用意したけど今回使っていないし、なくても困らない気もするので書いていない。

 public class ClickablieView : BoxView {
     public static readonly BindableProperty IsPointerDownProperty;
     public static readonly BindableProperty ForegroundColorProperty;

     public bool IsPointerDown { get; set; }
     public Color ForegroundColor { get; set; }

     public event EventHandler<EventArgs> Clicked;
 }

できたところでXAMLをかく。作ったClickableViewを下に、上にLabelを載せることでボタンぽい見た目としている。定義したForegroundColorはそのまま使用するのではなく、ボタンのテキスト色としてバインドする用途に使う。

 <!-- ボタンのコンテナ -->
 <Grid WidthRequest="128" HeightRequest="48">
  <c:ClickableView x:Name="button" ForegroundColor="White" >
    <View.Triggers>
      <Trigger TargetType="c:ClickableView" Property="IsPointerDown" Value="True">
        <Setter Property="BackgroundColor" Value="Black" />
        <Setter Property="ForegroundColor" Value="Red" />
      </Trigger>
    </View.Triggers>
    <View.Behaviors>
      <i:EventToCommand 
          EventName="Clicked"
          Command="{Binding ButtonClickCommand}" />
    </View.Behaviors>
  </c:ClickableView>
  <Label
    Style="{StaticResource ButtonTextStyle}" 
    Text="新規" 
    TextColor="{Binding Source={x:Reference button},Path=ForegroundColor}" />
 </Grid>

で、androidはこれで特に問題がないのだけどUWPはLabelがマウス処理イベントを奪うという問題に直面する。なんでそんなことになっているのか(UWPのデフォがそうなのか、xamarinがなにかやっているのか)調べてないけど、困るので対策を入れる。下記、

 public class AzLabelRenderer : LabelRenderer {
     private Reactive.Bindings.ReactiveProperty<bool> t;
     
     protected override void OnElementChanged(ElementChangedEventArgs<Label> e) {
         base.OnElementChanged(e);
         
         t = new Reactive.Bindings.ReactiveProperty<bool>();
         t.PropertyChanged += (s, ev) =>
         {
             if (t.Value) {
                 t.Value = false;
             }
         };
         
         data.BindingOperations.SetBinding(this,
             Windows.UI.Xaml.Controls.TextBlock.IsHitTestVisibleProperty,
             new data.Binding()
                 {
                     Path = new Windows.UI.Xaml.PropertyPath("Value"),
                     Source = t,
                     Mode = data.BindingMode.TwoWay,
                 });
         this.IsHitTestVisible = false;
     }
 }

IsHitTestが常にfalseとなるように細工する。特にReactivePropertyを使う必要はないのだけど、よそで使っているのにこれだけのためにクラス作るのもめどいので利用する。ビバ怠惰。

長くなったのでおわる。