Q058. XamDataGrid でセルに収まらない値をツールチップで表示するには?
A. いささか凝った方法ですが、MultiDataTrigger と IMultiValueConverter を使って設定します。
発端と問題
WindowsForms の DataGridView の場合、セルからはみ出したデータにマウス持ってくとツールチップが表示されるのですが、残念ながら WPF の標準 DataGrid も XamDataGrid もこの UI はサポートされてません。
当初どうしていいか全く判らず、苦肉の策で
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=Value}"/> <Style.Triggers> <DataTrigger Binding="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=Value}" Value=""> <Setter Property="ToolTip" Value="{x:Null}" /> </DataTrigger> </Style.Triggers>
なんてことをしてたのですが、Infaragistics さんに問い合わせてみたら DataTrigger とコンバーターを使う方法を教えて頂きました。
Option Explicit On Option Strict On Imports System.Globalization Imports System.Windows.Data Imports Infragistics.Windows.DataPresenter Class ValueToLengthConverter Implements IValueConverter Public Function Convert(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As CultureInfo) As Object Implements IValueConverter.Convert Dim myString As String If value Is Nothing Then Return False Else myString = value.ToString() If (myString.Length > 10) Then Return True Else Return False End If End If End Function Public Function ConvertBack(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As CultureInfo) As Object Implements IValueConverter.ConvertBack Return Nothing End Function End Class
<DataTrigger Binding="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=Value, Converter={StaticResource converter}}" Value="True"> <Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=Value}" /> </DataTrigger>
なるほど、Converter ってこうやって使うのか!と目から鱗だったのですが、残念ながら長さが固定されてしまうのでこのままだとうまくありません。シーンに応じてコンバーターのパラメータに長さを渡してやるといいですよとアドバイスを頂きましたが、Binding.ConverterParameter って悲しいかなリテラルしか渡せないためバインドデータが渡せません。(;_;)
MultiDataTrigger と IMultiValueConverter の組み合わせで解決
どうにかしてセル幅とさらにフォント情報も渡せないかと検索しまくってたら、MultiDataTrigger と IMultiValueConverter の組み合わせが見つかりました。
で、応用したパターンがこれ。MultiValueConverter にActualWidth と Value はもちろんフォント情報もごっそり渡し、コンバーター内で FormattedText クラスのインスタンスを生成して文字列の幅とセル幅を比較するという方法を採用してみました。
Option Explicit On Option Strict On Imports System.Globalization Imports System.Windows.Data Imports Infragistics.Windows.DataPresenter Public Class ValueToLengthConverter Implements IMultiValueConverter Private Enum Path ActualWidth FontFamily FontSize FontStretch FontStyle FontWeight Value End Enum Public Function Convert(values() As Object, targetType As System.Type, parameter As Object, culture As CultureInfo) As Object Implements IMultiValueConverter.Convert Dim myFontWidth = GetFontLength(values) Dim myWidth = GetWidth(values) If (myWidth <> 0) AndAlso (myFontWidth > myWidth) Then Return True Else Return False End If End Function Public Function ConvertBack(value As Object, targetTypes() As System.Type, parameter As Object, culture As CultureInfo) As Object() Implements IMultiValueConverter.ConvertBack Return Nothing End Function Private Function GetFontLength(values() As Object) As Double Dim ret As Double = 0.0F If values IsNot Nothing AndAlso values(Path.FontFamily) IsNot Nothing Then Dim value = GetValue(values) Dim fontFamily = CType(values(Path.FontFamily), FontFamily) Dim fontSize = CType(values(Path.FontSize), Double) Dim fontStrech = CType(values(Path.FontStretch), FontStretch) Dim fontStyle = CType(values(Path.FontStyle), FontStyle) Dim fontWeight = CType(values(Path.FontWeight), FontWeight) Dim ft As FormattedText = New FormattedText(value, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, New Typeface(fontFamily, fontStyle, fontWeight, fontStrech), fontSize, Brushes.Black) ret = ft.Width + fontSize ' fontSize は補正値 End If Return ret End Function Private Function GetValue(values() As Object) As String Dim ret As String = String.Empty If values IsNot Nothing AndAlso values(Path.Value) IsNot Nothing Then ret = values(Path.Value).ToString() End If Return ret End Function Private Function GetWidth(values() As Object) As Double Dim ret As Double = 0.0F If values IsNot Nothing AndAlso values(Path.ActualWidth) IsNot Nothing Then ret = CDbl(values(Path.ActualWidth)) End If Return ret End Function End Class
C# 版のコードです。
using System; using System.Globalization; using System.Windows; using System.Windows.Data; using System.Windows.Media; public class ValueToLengthConverter : IMultiValueConverter { private enum Path : int { ActualWidth = 0, FontFamily, FontSize, FontStretch, FontStyle, FontWeight, Value } public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { bool ret = false; var myFontWidth = GetFontLength(values); var myWidth = GetWidth(values); if ((myWidth != 0) && (myFontWidth > myWidth)) { ret = true; } return ret; } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) { return null; } private double GetFontLength(Object[] values) { double ret = 0d; if ((values != null) && (values[(int)Path.FontFamily] != null)) { var value = GetValue(values); var fontFamily = (FontFamily)values[(int)Path.FontFamily]; var fontSize = (double)values[(int)Path.FontSize]; var fontStrech = (FontStretch)values[(int)Path.FontStretch]; var fontStyle = (FontStyle)values[(int)Path.FontStyle]; var fontWeight = (FontWeight)values[(int)Path.FontWeight]; var ft = new FormattedText(value, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface(fontFamily, fontStyle, fontWeight, fontStrech), fontSize, Brushes.Black); ret = ft.Width + fontSize / 2 ; // fontSize / 2 は補正値 } return ret; } private string GetValue(Object[] values) { string ret = string.Empty; if ((values != null) && (values[(int)Path.Value] != null)) { ret = values[(int)Path.Value].ToString(); } return ret; } private double GetWidth(Object[] values) { double ret = 0d; if ((values != null) && (values[(int)Path.ActualWidth] != null)) { ret = (double)values[(int)Path.ActualWidth]; } return ret; } }
<Window x:Class="MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:igDP="http://infragistics.com/DataPresenter" xmlns:local="clr-namespace:WpfApplication1" Title="MainWindow" Height="350" Width="525" > <Window.Resources> <local:ValueToLengthConverter x:Key="converter" /> <Style TargetType="{x:Type igDP:CellValuePresenter}"> <Style.Triggers> <MultiDataTrigger> <MultiDataTrigger.Conditions> <Condition Value="True"> <Condition.Binding> <MultiBinding Converter="{StaticResource converter}"> <Binding Path="ActualWidth" RelativeSource="{RelativeSource self}" /> <Binding Path="FontFamily" RelativeSource="{RelativeSource self}" /> <Binding Path="FontSize" RelativeSource="{RelativeSource self}" /> <Binding Path="FontStretch" RelativeSource="{RelativeSource self}" /> <Binding Path="FontStyle" RelativeSource="{RelativeSource self}" /> <Binding Path="FontWeight" RelativeSource="{RelativeSource self}" /> <Binding Path="Value" RelativeSource="{RelativeSource self}" /> </MultiBinding> </Condition.Binding> </Condition> </MultiDataTrigger.Conditions> <Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=Value}" /> </MultiDataTrigger> </Style.Triggers> </Style> </Window.Resources> <Grid> <Grid.RowDefinitions> <RowDefinition Height="50" /> <RowDefinition /> </Grid.RowDefinitions> <igDP:XamDataGrid Grid.Row="1" BindToSampleData="True" /> </Grid> </Window>
するとこうなります。
この Tips ホントに需要あるのか怪しいですが、Forms で出来たことが WPF だと出来なくなってるよー!というお叱りに対処するには欠かせない(?)テクニックですよね(汗