MVVM: 보기 모델에 라디오 버튼을 바인딩하시겠습니까?
편집: .NET 4.0에서 문제가 해결되었습니다.
하여 라디오 버튼 그룹을 뷰 했습니다.IsChecked
단추를 채우다다른 게시물을 검토한 결과,IsChecked
재산은 단순히 작동하지 않습니다.아래에 포함된 문제를 재현한 짧은 데모를 작성했습니다.
여기 제 질문이 있습니다.MVVM을 사용하여 라디오 버튼을 바인딩하는 간단하고 신뢰할 수 있는 방법이 있습니까?감사해요.
추가 정보:그IsChecked
속성이 작동하지 않는 이유는 두 가지입니다.
단추를 선택하면 그룹에 있는 다른 단추의 확인됨 속성이 false로 설정되지 않습니다.
버튼을 선택하면 버튼을 처음 선택한 후 자체 IsChecked 속성이 설정되지 않습니다.나는 바인딩이 첫 클릭에 WPF에 의해 폐기되고 있다고 추측합니다.
데모 프로젝트:여기 문제를 재현하는 간단한 데모의 코드와 마크업이 있습니다.WPF 프로젝트를 생성하고 Window1.xaml의 마크업을 다음으로 바꿉니다.
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300" Loaded="Window_Loaded">
<StackPanel>
<RadioButton Content="Button A" IsChecked="{Binding Path=ButtonAIsChecked, Mode=TwoWay}" />
<RadioButton Content="Button B" IsChecked="{Binding Path=ButtonBIsChecked, Mode=TwoWay}" />
</StackPanel>
</Window>
Window1.xaml.cs 의 코드를 보기 모델을 설정하는 다음 코드(해킹)로 바꿉니다.
using System.Windows;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.DataContext = new Window1ViewModel();
}
}
}
를 이제다코드프추가니다합에트로 추가하세요.Window1ViewModel.cs
:
using System.Windows;
namespace WpfApplication1
{
public class Window1ViewModel
{
private bool p_ButtonAIsChecked;
/// <summary>
/// Summary
/// </summary>
public bool ButtonAIsChecked
{
get { return p_ButtonAIsChecked; }
set
{
p_ButtonAIsChecked = value;
MessageBox.Show(string.Format("Button A is checked: {0}", value));
}
}
private bool p_ButtonBIsChecked;
/// <summary>
/// Summary
/// </summary>
public bool ButtonBIsChecked
{
get { return p_ButtonBIsChecked; }
set
{
p_ButtonBIsChecked = value;
MessageBox.Show(string.Format("Button B is checked: {0}", value));
}
}
}
}
문제를 재현하려면 앱을 실행하고 A 버튼을 클릭합니다.의 "A"에 메시지 IsChecked
속성이 true로 설정되었습니다.이제 버튼 B를 선택합니다.버튼 B의 메시지 상자가 나타납니다.IsChecked
속성이 true로 설정되었지만 버튼 A의 메시지 상자가 없습니다.IsChecked
속성이 false로 설정되었습니다. 속성은 변경되지 않았습니다.
이제 A 버튼을 다시 클릭합니다.이 버튼은 창에서 선택되지만 메시지 상자는 나타나지 않습니다.IsChecked
속성이 변경되지 않았습니다.마지막으로 버튼 B를 다시 클릭합니다. 같은 결과입니다. 그IsChecked
버튼을 처음 클릭한 후 두 버튼에 대해 속성이 전혀 업데이트되지 않습니다.
제이슨의 한다면, 단, 는 매우 됩니다.ListBox
그 시점에서 스타일링을 적용하는 것은 사소한 일입니다.ListBox
하여 ▁control▁so시▁up .RadioButton
리스트에합니다.
<ListBox ItemsSource="{Binding ...}" SelectedItem="{Binding ...}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<RadioButton Content="{TemplateBinding Content}"
IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsSelected}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
그들이 고정시킨 것처럼 보입니다.IsChecked
.4의...NET 4의 속성입니다.VS2008에서 깨졌던 프로젝트가 VS2010에서 작동합니다.
앞으로 이 질문을 연구하는 모든 사람들의 이익을 위해, 여기 제가 궁극적으로 구현한 솔루션이 있습니다.그것은 제가 그 문제에 대한 최고의 해결책으로 선택한 존 보웬의 대답을 기반으로 합니다.
먼저 라디오 버튼이 포함된 투명 목록 상자의 스타일을 항목으로 만들었습니다.그런 다음 목록 상자에 들어갈 버튼을 만들었습니다. 제 버튼은 앱에 데이터로 읽는 것이 아니라 고정되어 있기 때문에 마크업에 하드 코딩을 했습니다.
는 다을사용다니라는 합니다.ListButtons
목록 상자의 단추를 나타내는 보기 모델에서 각 단추의Tag
속성 - 해당 단추에 사용할 열거값의 문자열 값을 전달합니다. 그ListBox.SelectedValuePath
하면 속을사여다지수있다니습정을 할 수 .Tag
값의 하여 뷰 .SelectedValue
과 값을 하려면 값 했습니다.문자열과 enum 값 사이를 변환하려면 값 변환기가 필요할 것으로 생각했지만 WPF의 내장 변환기가 문제없이 변환을 처리했습니다.
다음은 Windows 1.xaml에 대한 전체 마크업입니다.
<Window x:Class="RadioButtonMvvmDemo.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<!-- Resources -->
<Window.Resources>
<Style x:Key="RadioButtonList" TargetType="{x:Type ListBox}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}" >
<Setter Property="Margin" Value="5" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border BorderThickness="0" Background="Transparent">
<RadioButton
Focusable="False"
IsHitTestVisible="False"
IsChecked="{TemplateBinding IsSelected}">
<ContentPresenter />
</RadioButton>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBox}">
<Border BorderThickness="0" Padding="0" BorderBrush="Transparent" Background="Transparent" Name="Bd" SnapsToDevicePixels="True">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<!-- Layout -->
<Grid>
<!-- Note that we use SelectedValue, instead of SelectedItem. This allows us
to specify the property to take the value from, using SelectedValuePath. -->
<ListBox Style="{StaticResource RadioButtonList}" SelectedValuePath="Tag" SelectedValue="{Binding Path=SelectedButton}">
<ListBoxItem Tag="ButtonA">Button A</ListBoxItem>
<ListBoxItem Tag="ButtonB">Button B</ListBoxItem>
</ListBox>
</Grid>
</Window>
뷰 모델에는 ListButton 열거를 사용하여 선택된 버튼을 표시하는 SelectedButton이라는 단일 속성이 있습니다.모델에 은 뷰사기본클이호출며이하는, 성를의를 .PropertyChanged
이벤트:
namespace RadioButtonMvvmDemo
{
public enum ListButtons {ButtonA, ButtonB}
public class Window1ViewModel : ViewModelBase
{
private ListButtons p_SelectedButton;
public Window1ViewModel()
{
SelectedButton = ListButtons.ButtonB;
}
/// <summary>
/// The button selected by the user.
/// </summary>
public ListButtons SelectedButton
{
get { return p_SelectedButton; }
set
{
p_SelectedButton = value;
base.RaisePropertyChangedEvent("SelectedButton");
}
}
}
}
의 프로덕션에서, 프션앱서에덕.SelectedButton
setter는 버튼을 선택할 때 필요한 조치를 취할 서비스 클래스 메소드를 호출합니다.
기본 클래스는 다음과 같습니다.
using System.ComponentModel;
namespace RadioButtonMvvmDemo
{
public abstract class ViewModelBase : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region Protected Methods
/// <summary>
/// Raises the PropertyChanged event.
/// </summary>
/// <param name="propertyName">The name of the changed property.</param>
protected void RaisePropertyChangedEvent(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);
PropertyChanged(this, e);
}
}
#endregion
}
}
도움이 되길 바랍니다!
한 가지 해결책은 속성 설정기의 라디오 버튼에 대한 보기 모델을 업데이트하는 것입니다.버튼 A가 True로 설정되면 버튼 B를 false로 설정합니다.
DataContext에서 개체에 바인딩할 때 또 다른 중요한 요소는 개체가 INOTIFY를 구현해야 한다는 것입니다.속성이 변경되었습니다.바인딩된 속성이 변경되면 이벤트가 실행되고 변경된 속성의 이름이 포함되어야 합니다. (간단히 설명하기 위해 샘플에서 Null 확인은 생략됨)
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool _ButtonAChecked = true;
public bool ButtonAChecked
{
get { return _ButtonAChecked; }
set
{
_ButtonAChecked = value;
PropertyChanged(this, new PropertyChangedEventArgs("ButtonAChecked"));
if (value) ButtonBChecked = false;
}
}
protected bool _ButtonBChecked;
public bool ButtonBChecked
{
get { return _ButtonBChecked; }
set
{
_ButtonBChecked = value;
PropertyChanged(this, new PropertyChangedEventArgs("ButtonBChecked"));
if (value) ButtonAChecked = false;
}
}
}
편집:
문제는 버튼 B를 처음 클릭할 때 IsChecked 값이 변경되고 바인딩이 피드되지만 버튼 A가 선택되지 않은 상태를 버튼으로 피드되지 않는다는 것입니다.확인된 속성입니다.코드에서 수동으로 버튼을 업데이트합니다.다음에 A 버튼을 클릭할 때 확인된 속성 설정기가 호출됩니다.
여기 당신이 할 수 있는 다른 방법이 있습니다.
보기:
<StackPanel Margin="90,328,965,389" Orientation="Horizontal">
<RadioButton Content="Mr" Command="{Binding TitleCommand, Mode=TwoWay}" CommandParameter="{Binding Content, RelativeSource={RelativeSource Mode=Self}, Mode=TwoWay}" GroupName="Title"/>
<RadioButton Content="Mrs" Command="{Binding TitleCommand, Mode=TwoWay}" CommandParameter="{Binding Content, RelativeSource={RelativeSource Mode=Self}, Mode=TwoWay}" GroupName="Title"/>
<RadioButton Content="Ms" Command="{Binding TitleCommand, Mode=TwoWay}" CommandParameter="{Binding Content, RelativeSource={RelativeSource Mode=Self}, Mode=TwoWay}" GroupName="Title"/>
<RadioButton Content="Other" Command="{Binding TitleCommand, Mode=TwoWay}" CommandParameter="{Binding Content, RelativeSource={RelativeSource Mode=Self}}" GroupName="Title"/>
<TextBlock Text="{Binding SelectedTitle, Mode=TwoWay}"/>
</StackPanel>
모델 보기:
private string selectedTitle;
public string SelectedTitle
{
get { return selectedTitle; }
set
{
SetProperty(ref selectedTitle, value);
}
}
public RelayCommand TitleCommand
{
get
{
return new RelayCommand((p) =>
{
selectedTitle = (string)p;
});
}
}
IsChecked 버그에 대한 확신이 없습니다. 뷰 모델에 적용할 수 있는 하나의 가능한 리팩터입니다. 뷰에는 일련의 라디오 버튼으로 표시되는 여러 상호 배타적 상태가 있으며, 그 중 한 번에 하나만 선택할 수 있습니다.뷰 모델에서 상태 A, 상태 B 등 가능한 상태를 나타내는 속성(예: 열거형)을 하나만 가지면 모든 개별 버튼이 필요하지 않습니다.확인된 AI 등
존 보웬의 대답에 대한 약간의 확장:값이 구현되지 않으면 작동하지 않습니다.ToString()
설정하는 대신 필요한 사항Content
합니다.ContentPresenter
: 안에서게, 렇이그:
<ListBox ItemsSource="{Binding ...}" SelectedItem="{Binding ...}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<RadioButton IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsSelected}">
<ContentPresenter/>
</RadioButton>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
이방으로사수있다니습용할추가를 추가로 사용할 수 .DisplayMemberPath
는또.ItemTemplate
은 항목을을 제공합니다.라디오 버튼은 항목을 "랩"하여 선택 항목을 제공합니다.
저는 이것이 오래된 질문이고 원래의 문제는 .NET 4에서 해결되었다는 것을 알고 있습니다. 그리고 솔직히 말해서 이것은 주제에서 약간 벗어난 것입니다.
MVVM에서 RadioButtons를 사용하려는 대부분의 경우 열거형 요소를 선택하는 것입니다. 이 경우 VM 공간의 bool 속성을 각 버튼에 바인딩하고 이를 사용하여 실제 선택 내용을 반영하는 전체 열거형 속성을 설정해야 하므로 매우 빠르게 지루해집니다.그래서 저는 재사용이 가능하고 구현이 매우 쉬우며 ValueConverter가 필요 없는 솔루션을 개발했습니다.
보기는 거의 동일하지만 열거형을 배치한 후에는 단일 속성으로 VM 측을 수행할 수 있습니다.
주 창VM
using System.ComponentModel;
namespace EnumSelectorTest
{
public class MainWindowVM : INotifyPropertyChanged
{
public EnumSelectorVM Selector { get; set; }
private string _colorName;
public string ColorName
{
get { return _colorName; }
set
{
if (_colorName == value) return;
_colorName = value;
RaisePropertyChanged("ColorName");
}
}
public MainWindowVM()
{
Selector = new EnumSelectorVM
(
typeof(MyColors),
MyColors.Red,
false,
val => ColorName = "The color is " + ((MyColors)val).ToString()
);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
모든 작업을 수행하는 클래스는 DynamicObject에서 상속됩니다.외부에서 보면 XAML에서 바인딩할 수 있는 'Is', 'IsRed', 'IsBlue' 등으로 접두사가 붙은 열거형의 각 요소에 대해 bool 속성을 생성합니다.실제 열거값을 보유하는 Value 속성과 함께 사용합니다.
public enum MyColors
{
Red,
Magenta,
Green,
Cyan,
Blue,
Yellow
}
Enum SelectorVM
using System;
using System.ComponentModel;
using System.Dynamic;
using System.Linq;
namespace EnumSelectorTest
{
public class EnumSelectorVM : DynamicObject, INotifyPropertyChanged
{
//------------------------------------------------------------------------------------------------------------------------------------------
#region Fields
private readonly Action<object> _action;
private readonly Type _enumType;
private readonly string[] _enumNames;
private readonly bool _notifyAll;
#endregion Fields
//------------------------------------------------------------------------------------------------------------------------------------------
#region Properties
private object _value;
public object Value
{
get { return _value; }
set
{
if (_value == value) return;
_value = value;
RaisePropertyChanged("Value");
_action?.Invoke(_value);
}
}
#endregion Properties
//------------------------------------------------------------------------------------------------------------------------------------------
#region Constructor
public EnumSelectorVM(Type enumType, object initialValue, bool notifyAll = false, Action<object> action = null)
{
if (!enumType.IsEnum)
throw new ArgumentException("enumType must be of Type: Enum");
_enumType = enumType;
_enumNames = enumType.GetEnumNames();
_notifyAll = notifyAll;
_action = action;
//do last so notification fires and action is executed
Value = initialValue;
}
#endregion Constructor
//------------------------------------------------------------------------------------------------------------------------------------------
#region Methods
//---------------------------------------------------------------------
#region Public Methods
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
string elementName;
if (!TryGetEnumElemntName(binder.Name, out elementName))
{
result = null;
return false;
}
try
{
result = Value.Equals(Enum.Parse(_enumType, elementName));
}
catch (Exception ex) when (ex is ArgumentNullException || ex is ArgumentException || ex is OverflowException)
{
result = null;
return false;
}
return true;
}
public override bool TrySetMember(SetMemberBinder binder, object newValue)
{
if (!(newValue is bool))
return false;
string elementName;
if (!TryGetEnumElemntName(binder.Name, out elementName))
return false;
try
{
if((bool) newValue)
Value = Enum.Parse(_enumType, elementName);
}
catch (Exception ex) when (ex is ArgumentNullException || ex is ArgumentException || ex is OverflowException)
{
return false;
}
if (_notifyAll)
foreach (var name in _enumNames)
RaisePropertyChanged("Is" + name);
else
RaisePropertyChanged("Is" + elementName);
return true;
}
#endregion Public Methods
//---------------------------------------------------------------------
#region Private Methods
private bool TryGetEnumElemntName(string bindingName, out string elementName)
{
elementName = "";
if (bindingName.IndexOf("Is", StringComparison.Ordinal) != 0)
return false;
var name = bindingName.Remove(0, 2); // remove first 2 chars "Is"
if (!_enumNames.Contains(name))
return false;
elementName = name;
return true;
}
#endregion Private Methods
#endregion Methods
//------------------------------------------------------------------------------------------------------------------------------------------
#region Events
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion Events
}
}
변경사항에 응답하려면 알림에 가입할 수 있습니다.Property 위와 같이 이벤트를 변경하거나 익명 메서드를 생성자에게 전달합니다.
그리고 마지막으로 메인 윈도우.xaml
<Window x:Class="EnumSelectorTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel>
<RadioButton IsChecked="{Binding Selector.IsRed}">Red</RadioButton>
<RadioButton IsChecked="{Binding Selector.IsMagenta}">Magenta</RadioButton>
<RadioButton IsChecked="{Binding Selector.IsBlue}">Blue</RadioButton>
<RadioButton IsChecked="{Binding Selector.IsCyan}">Cyan</RadioButton>
<RadioButton IsChecked="{Binding Selector.IsGreen}">Green</RadioButton>
<RadioButton IsChecked="{Binding Selector.IsYellow}">Yellow</RadioButton>
<TextBlock Text="{Binding ColorName}"/>
</StackPanel>
</Grid>
</Window>
다른 사람이 이걸 유용하게 찾았으면 좋겠네요, 왜냐면 이건 제 도구함에 들어가는 것 같아요.
라디오 단추의 그룹 이름을 추가해야 합니다.
<StackPanel>
<RadioButton Content="Button A" IsChecked="{Binding Path=ButtonAIsChecked, Mode=TwoWay}" GroupName="groupName" />
<RadioButton Content="Button B" IsChecked="{Binding Path=ButtonBIsChecked, Mode=TwoWay}" GroupName="groupName" />
</StackPanel>
VS2015와 .NET 4.5.1에서도 매우 유사한 문제가 있습니다.
XAML:
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="6" Rows="1"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate >
<RadioButton GroupName="callGroup" Style="{StaticResource itemListViewToggle}" Click="calls_ItemClick" Margin="1" IsChecked="{Binding Path=Selected,Mode=TwoWay}" Unchecked="callGroup_Checked" Checked="callGroup_Checked">
....
이 코드에서 볼 수 있듯이 목록 보기가 있으며 템플릿의 항목은 그룹 이름에 속하는 라디오 단추입니다.
Selected 속성이 True로 설정된 상태에서 컬렉션에 새 항목을 추가하면 선택된 것으로 나타나고 나머지 버튼은 선택된 상태로 유지됩니다.
먼저 체크된 버튼을 받고 수동으로 false로 설정하여 해결하지만, 원래 이런 방식은 아닙니다.
코드 뒤:
`....
lstInCallList.ItemsSource = ContactCallList
AddHandler ContactCallList.CollectionChanged, AddressOf collectionInCall_change
.....
Public Sub collectionInCall_change(sender As Object, e As NotifyCollectionChangedEventArgs)
'Whenever collection change we must test if there is no selection and autoselect first.
If e.Action = NotifyCollectionChangedAction.Add Then
'The solution is this, but this shouldn't be necessary
'Dim seleccionado As RadioButton = getCheckedRB(lstInCallList)
'If seleccionado IsNot Nothing Then
' seleccionado.IsChecked = False
'End If
DirectCast(e.NewItems(0), PhoneCall).Selected = True
.....
End sub
`
<RadioButton IsChecked="{Binding customer.isMaleFemale}">Male</RadioButton>
<RadioButton IsChecked="{Binding customer.isMaleFemale,Converter= {StaticResource GenderConvertor}}">Female</RadioButton>
아래는 IV ValueConverter용 코드입니다.
public class GenderConvertor : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return !(bool)value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return !(bool)value;
}
}
이것은 나에게 효과가 있었습니다.라디오 버튼 클릭에 따라 뷰 모델과 뷰 모델 모두에 짝수 값이 바인딩되었습니다.참--> 수컷과 거짓--> 암컷
언급URL : https://stackoverflow.com/questions/2284752/mvvm-binding-radio-buttons-to-a-view-model
'programing' 카테고리의 다른 글
포스트그레스를 사용하여 간격을 몇 시간으로 변환하려면 어떻게 해야 합니까? (0) | 2023.05.26 |
---|---|
원격 호스트에서 포트 하나의 상태 확인 (0) | 2023.05.26 |
Git을 사용하여 프로젝트의 최신 개정판을 얻는 방법은 무엇입니까? (0) | 2023.05.26 |
Eclipse Classic에서 Eclipse Marketplace를 설치하려면 어떻게 해야 합니까? (0) | 2023.05.26 |
git는 파일을 어떻게 저장합니까? (0) | 2023.05.26 |