web-dev-qa-db-ja.com

WPFのデータバインドされたラジオボタンリスト

データオブジェクトにオプションのリストがあり、ユーザーがそれらの1つだけを選択できるようにラジオボタンリストと同等のものを作成したいと思います。データバインドされたコンボボックスと同様の機能ですが、ラジオボタン形式です。

愚かな私、これは組み込まれると思いましたが、違います。どうやってやるの?

17
Scott O.

基本的に、グーグルの結果を確認した後、私は WPF博士が答えを提供したMSDNディスカッションスレッド からの情報から始めました。これは、リストボックスを正しく見せるスタイルについて説明しています。ただし、リストボックスが無効になっている場合、背景は、私が読むまで、私が一生取り除くことができなかった厄介な色でした ListBox ControlTemplateのMSDNの例 、これは秘密を示しています私の背景のお尻を蹴っていたボーダー要素。

したがって、ここでの最終的な答えは次のスタイルでした。

<Style x:Key="RadioButtonList" TargetType="{x:Type ListBox}">
    <!-- ControlTemplate taken from MSDN http://msdn.Microsoft.com/en-us/library/ms754242.aspx -->
    <Setter Property="SnapsToDevicePixels" Value="true"/>
    <Setter Property="OverridesDefaultStyle" Value="true"/>
    <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
    <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
    <Setter Property="ScrollViewer.CanContentScroll" Value="true"/>
    <Setter Property="MinWidth" Value="120"/>
    <Setter Property="MinHeight" Value="95"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ListBox">
                <Border Name="Border" Background="Transparent"
                        BorderBrush="Transparent"
                        BorderThickness="0"
                        CornerRadius="2">
                    <ScrollViewer Margin="0" Focusable="false">
                        <StackPanel Margin="2" IsItemsHost="True" />
                    </ScrollViewer>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsEnabled" Value="false">
                        <Setter TargetName="Border" Property="Background"
                                Value="Transparent" />
                        <Setter TargetName="Border" Property="BorderBrush"
                                Value="Transparent" />
                    </Trigger>
                    <Trigger Property="IsGrouping" Value="true">
                        <Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Setter Property="ItemContainerStyle">
        <Setter.Value>
            <Style TargetType="{x:Type ListBoxItem}" >
                <Setter Property="Margin" Value="2" />
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type ListBoxItem}">
                            <Border Name="theBorder" Background="Transparent">
                                <RadioButton Focusable="False" IsHitTestVisible="False"
                                             IsChecked="{TemplateBinding IsSelected}">
                                    <ContentPresenter />
                                </RadioButton>
                            </Border>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </Setter.Value>
    </Setter>
</Style>

これは、リストボックスとアイテムのControlTemplateとスタイルを提供します。そしてそれはこのように使用されます:

<ListBox Grid.Column="1" Grid.Row="0" x:Name="TurnChargeBasedOnSelector" Background="Transparent"
    IsEnabled="{Binding Path=IsEditing}"
    Style="{StaticResource RadioButtonList}"
    ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:MainForm}}, Path=DataContext.RampTurnsBasedOnList}"
    DisplayMemberPath="Description" SelectedValuePath="RampTurnsBasedOnID"
    SelectedValue="{Binding Path=RampTurnsBasedOnID, NotifyOnValidationError=True, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}"/>

WPFに時間を費やすほど、些細なことはめちゃくちゃ難しくなり、めちゃくちゃ難しいことになると思います。楽しい。 -スコット

27
Scott O.

プロパティNameを持つオブジェクトのリストを使用してリストボックスをListBoxのItemsSourceにバインドします(これは変更される可能性があります)

<ListBox Name="RadioButtonList">
   <ListBox.ItemTemplate >
        <DataTemplate >
             <RadioButton GroupName="radioList" Tag="{Binding}" Content="{Binding Name}"/>
         </DataTemplate>
                                                    </ListBox.ItemTemplate>
                                                </ListBox>

重要GroupName = "radioList"

14
Raul romero

これは、ValueConverterenumに変換するboolを介して行いました。ラジオボタンがConverterParameterとして表す列挙値を渡すことにより、コンバーターはこのラジオボタンをチェックする必要があるかどうかを返します。

<Window.Resources>
    <Converters:EnumConverter x:Key="EnumConverter" />
</Window.Resources>

<RadioButton IsChecked="{Binding Path=MyEnum, Mode=TwoWay, 
                                 Converter={StaticResource EnumConverter}, 
                                 ConverterParameter=Enum1}"}
             Content="Enum 1" />
<RadioButton IsChecked="{Binding Path=MyEnum, Mode=TwoWay, 
                                 Converter={StaticResource EnumConverter}, 
                                 ConverterParameter=Enum2}"}
             Content="Enum 2" />

EnumConverterは次のように定義されます。

public class EnumConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (targetType.IsAssignableFrom(typeof(Boolean)) && targetType.IsAssignableFrom(typeof(String)))
                throw new ArgumentException("EnumConverter can only convert to boolean or string.");
            if (targetType == typeof(String))
                return value.ToString();

            return String.Compare(value.ToString(), (String)parameter, StringComparison.InvariantCultureIgnoreCase) == 0;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (targetType.IsAssignableFrom(typeof(Boolean)) && targetType.IsAssignableFrom(typeof(String)))
                throw new ArgumentException("EnumConverter can only convert back value from a string or a boolean.");
            if (!targetType.IsEnum)
                throw new ArgumentException("EnumConverter can only convert value to an Enum Type.");

            if (value.GetType() == typeof(String))
            {
                return Enum.Parse(targetType, (String)value, true);
            }

            //We have a boolean, as for binding to a checkbox. we use parameter
            if ((Boolean)value)
                return Enum.Parse(targetType, (String)parameter, true);

            return null;
        }
    }

列挙型のリストにデータバインドしてラジオボタンを生成するのではなく、手動で行っていることに注意してください。バインディングを介してラジオボタンのリストを埋めたい場合は、IsCheckedバインディングを現在の値とラジオの列挙値の両方にバインドするMultiBindingに変更する必要があると思います。 、ConverterParameterでバインディングを使用できないため。

7
Anthony Brien

非常にシンプルでMVVMに対応し、タイプにDataTemplatesを活用します。 XAML:

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApplication1"
    Title="MainWindow" Height="350" Width="525">

<Window.Resources>
    <DataTemplate DataType="{x:Type local:Option}">
        <RadioButton Focusable="False"
                IsHitTestVisible="False"
                Content="{Binding Display}"
                IsChecked="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}">
        </RadioButton>
    </DataTemplate>
</Window.Resources>

<Grid>
    <ListBox ItemsSource="{Binding Options}" SelectedItem="{Binding SelectedOption}"/>
</Grid>

モデルの表示など:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = new Vm();
    }
}

public class Vm
{
    public Option[] Options { get { return new Option[] { 
        new Option() { Display = "A" }, 
        new Option() { Display = "B" }, 
        new Option() { Display = "C" } }; } }
    public Option SelectedOption { get; set; }
}

public class Option
{
    public string Display { get; set; }
}

オプションを特定のタイプにラップする場合(またはすでにそうである可能性があります)。そのタイプにDataTemplateを設定するだけで、WPFが自動的にそれを使用します。 (リストボックスリソースでDataTemplateを定義して、DataTemplateが適用される場所の範囲を制限します)。

また、必要に応じて、DataTemplateでグループ名を使用してグループを設定します。

これは、コントロールテンプレートを変更するよりもはるかに簡単ですが、選択したアイテムに青い線が表示されることを意味します。 (繰り返しますが、少しのスタイリングで修正できないものはありません)。

方法を知っていれば、WPFは単純です。

6
user619417

申し訳ありませんが、この回答をスコットOの投稿へのコメントとして投稿したいと思いますが、まだその評判はありません。彼の答えはスタイルのみのソリューションであり、コードビハインドやカスタムコントロールの作成などを追加する必要がなかったので、私は本当に気に入りました。

ただし、ListBoxItems内のコントロールを使用してみたときに、1つの問題が発生しました。このスタイルを使用すると、次の行が原因で、含まれているコントロールのいずれにもフォーカスできません。

<RadioButton Focusable="False" IsHitTestVisible="False" IsChecked="{TemplateBinding IsSelected}">

IsCheckedバインディングが正しく機能するには、ラジオボタンでFocusableとIsHitTestVisibleをオフにする必要があります。これを回避するために、IsCheckedをTemplateBindingから通常のバインディングに変更しました。これにより、双方向バインディングにすることができました。問題のある設定を削除すると、次の行が表示されます。

<RadioButton IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsSelected, Mode=TwoWay}">

これにより、ListBoxItemsに含まれるすべてのコントロールを期待どおりにフォーカスできるようになりました。

お役に立てれば。

4
Lachlan L.

Jon Bensonのブログエントリ からインスピレーションを得ましたが、description属性を持つ列挙型を使用するように彼のソリューションを変更しました。したがって、ソリューションの重要な部分は次のようになりました。

説明付きの列挙子

public enum AgeRange {
  [Description("0 - 18 years")]
  Youth,
  [Description("18 - 65 years")]
  Adult,
  [Description("65+ years")]
  Senior,
}

説明を読み取り、バインドのためにキーと値のペアを返すためのコード。

public static class EnumHelper
{
    public static string ToDescriptionString(this Enum val)
    {
        var attribute =
            (DescriptionAttribute)
            val.GetType().GetField(val.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false).
                SingleOrDefault();
        return attribute == default(DescriptionAttribute) ? val.ToString() : attribute.Description;
    }

    public static List<KeyValuePair<string,string>> GetEnumValueDescriptionPairs(Type enumType)
    {
        return Enum.GetValues(enumType)
            .Cast<Enum>()
            .Select(e => new KeyValuePair<string, string>(e.ToString(), e.ToDescriptionString()))
            .ToList();
    }
}

XAMLのオブジェクトデータプロバイダー

<ObjectDataProvider
    ObjectType="{x:Type local:EnumHelper}"
    MethodName="GetEnumValueDescriptionPairs"
    x:Key="AgeRanges">
    <ObjectDataProvider.MethodParameters>
        <x:Type TypeName="local:AgeRange" />
    </ObjectDataProvider.MethodParameters>
</ObjectDataProvider>

XAMLのリストボックス

<ListBox 
    ItemsSource="{Binding Source={StaticResource AgeRanges}}"
    SelectedValue="{Binding SelectedAgeRange}"
    SelectedValuePath="Key">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <RadioButton 
                IsChecked="{Binding IsSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}}"
                Content="{Binding Value}" />
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

バインドしているプロパティ(ビューモデルなど)

public class YourViewModel : INotifyPropertyChanged
{
  private AgeRange _selectedAgeRange;
  public AgeRange SelectedAgeRange
  {
    get { return _selectedAgeRange; }
    set 
    {
      if (value != _selectedAgeRange)
      {
        _selectedAgeRange = value;
        OnPropertyChanged("SelectedAgeRange");
      }
    }
  }
}
2
Brian Hinchey

私はだましました:

私の解決策は、リストボックスをプログラムでバインドすることでした。それが、私にとってはうまくいくように思えたからです。

            if (mUdData.Telephony.PhoneLst != null)
            {
                lbPhone.ItemsSource = mUdData.Telephony.PhoneLst;
                lbPhone.SelectedValuePath = "ID";
                lbPhone.SelectedValue = mUdData.Telephony.PrimaryFaxID;
            }

XAMLは次のようになります。

                        <ListBox.ItemTemplate >

                        <DataTemplate >
                            <Grid>
                                <Grid.RowDefinitions>
                                    <RowDefinition></RowDefinition>
                                </Grid.RowDefinitions>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="Auto"></ColumnDefinition>
                                    <ColumnDefinition Width="Auto"></ColumnDefinition>
                                </Grid.ColumnDefinitions>

                                <RadioButton 
                                    IsChecked="{Binding Path=PrimaryPhoneID}" 
                                    GroupName="Phone" 
                                    x:Name="rbPhone"
                                    Content="{Binding Path=PrimaryPhoneID}"
                                    Checked="rbPhone_Checked"/>

                                <CheckBox Grid.Column="2" IsEnabled="False" IsChecked="{Binding Path=Active}" Content="{Binding Path=Number}" ></CheckBox>

                            </Grid>
                        </DataTemplate>
                    </ListBox.ItemTemplate>

そして、私のイベントでは、選択されたラジオボタンの値を読み取ると、次のようになります。

    private void rbPhone_Checked(object sender, RoutedEventArgs e)
    {
        DataRowView dvFromControl = null;
        dvFromControl = (DataRowView)((RadioButton)sender).DataContext;

        BindData.Telephony.PrimaryPhoneID = (int)dvFromControl["ID"];

    }

それが誰かを助けることを願っています。

1
Alden Snow