Список связанных кнопок данных в WPF - PullRequest
17 голосов
/ 30 апреля 2009

У меня есть список параметров в объекте данных, и я хочу сделать эквивалент списка переключателей, чтобы позволить пользователю выбрать один и только один из них. Функциональность аналогична комбинированному списку с привязкой к данным, но в формате переключателей.

Глупый я, я думал, что это будет встроено, но нет. Как ты это делаешь?

Ответы [ 7 ]

27 голосов
/ 30 апреля 2009

По сути, после просмотра результатов поиска в Google я начал с информации из ветки обсуждения MSDN, где Dr. WPF предоставил ответ , в котором говорится о том, как оформить ListBox для правильного просмотра. Однако, когда список отключен, фон был раздражающим цветом, от которого я не мог избавиться до конца жизни, пока не прочитал пример MSDN для ListBox ControlTemplate , который показывает секретную границу элемент, который пинал мою заднюю задницу.

Итак, окончательный ответ здесь был такой:

<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 и элементов. И это привыкает так:

<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, тем больше думаю, что это делает тривиальное безумно трудным и безумно сложным тривиальным. Наслаждаться. -Скотт

14 голосов
/ 28 июля 2009

Свяжите список со Источником Items для ListBox со списком объектов, которые имеют свойство Name (это может измениться)

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

важно GroupName = "radioList"

7 голосов
/ 30 апреля 2009

Я сделал это через ValueConverter, который преобразует enum в bool. Передав значение enum, которое ваша радиокнопка представляет как 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.

6 голосов
/ 10 апреля 2013

Super Simple, 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 в ресурсах ListBox, чтобы ограничить область применения DataTemplate).

Также используйте имя группы в шаблоне данных, чтобы установить группу, если хотите.

Это гораздо проще, чем изменить шаблон элемента управления, однако это означает, что вы получите синюю линию на выбранных элементах. (Опять же, ничто, что немного стиля не может исправить).

WPF прост, когда вы знаете, как.

4 голосов
/ 21 декабря 2012

Извините, я хотел бы поместить этот ответ на пост Скотта О в качестве комментария к его сообщению, но у меня пока нет репутации, чтобы сделать это. Мне очень понравился его ответ, так как это было решение, основанное только на стиле, и, следовательно, не требовало добавления дополнительного кода или создания пользовательского элемента управления и т. Д.

Однако у меня была одна проблема, когда я попытался использовать элементы управления внутри ListBoxItems. Когда я использую этот стиль, я не могу сфокусировать какой-либо из содержащихся в нем элементов управления из-за этой строки:

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

Радиокнопка должна отключить Focusable и IsHitTestVisible, чтобы привязка IsChecked работала правильно. Чтобы обойти это, я изменил IsChecked с TemplateBinding на обычную привязку, что позволило мне сделать его двусторонней привязкой. Удаление оскорбительных настроек дало мне эту строку:

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

Что теперь позволяет мне сфокусировать любые элементы управления, содержащиеся в ListBoxItems, как и ожидалось.

Надеюсь, это поможет.

2 голосов
/ 27 сентября 2012

Я черпал вдохновение из записи в блоге Джона Бенсона , но изменил его решение, чтобы использовать перечисления с атрибутом 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");
      }
    }
  }
}
1 голос
/ 07 августа 2009

я обманул:

Мое решение состояло в том, чтобы связать список со списком программ, так как это все, что мне показалось подходящим:

            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"];

    }

Надеюсь, это кому-нибудь поможет.

...