Прежде всего, я начинающий WPF! Мой подход потенциально не является правильным способом делать то, что я хочу, поэтому не стесняйтесь сказать мне, если это так. То, что я хочу сделать, - это составной пользовательский элемент управления в WPF с использованием MVVM.Пользовательские пользовательские элементы управления WPF
Некоторые классы будут делать лучшее представление, чем я, вот мой взгляд модели:
interface IParameter : INotifyPropertyChanged
{
string Name { get; set;}
string Value { get; set;}
}
class TextParameter : ViewModelBase, IParameter
{
private string _value;
public string Name { get; private set; }
public string Value
{
get { return _value; }
set
{
_value = value;
RaisePropertyChanged();
}
}
public TextParameter (string name)
{
this.Name = name;
}
}
class ParameterList : ViewModelBase, IParameter
{
private string _value;
public string Name { get; private set; }
public string Value
{
get { return _value; }
set
{
_value = value;
RaisePropertyChanged();
}
}
ObservableCollection<IParameter> Parameters { get; set; }
public ParameterList (string name, IEnumerable<IParameter> parameters = null)
{
this.Name = name;
this.Parameters = new ObservableCollection<IParameter>(parameters ?? new List<IParameter>());
}
}
Я использую MVVM Light, так что весь материал PropertyChanged управляется в ViewModelBase. Кроме того, это не исчерпывающий список всех параметров, есть некоторые другие, более сложные, но проблема в этих проблемах.
Вот мои пользовательские элементы управления пользователя:
TextParameterControl.xaml:
<UserControl x:Class="Stuff.TextParameterControl" [..] x:Name="parent">
<StackPanel DataContext="{Binding ElementName=parent}" Orientation="Horizontal">
<TextBlock Text="{Binding Path=ParamName, StringFormat='{}{0}:'}" Width="100"></TextBlock>
<TextBox Text="{Binding Path=Value}" Width="100"></TextBox>
</StackPanel>
</UserControl>
TextParameterControl.xaml.cs:
public class TextParameterControl : UserControl
{
#region param name
public string ParamName
{
get { return (string)GetValue(ParamNameProperty); }
set { SetValue(ParamNameProperty, value); }
}
// Using a DependencyProperty as the backing store for ParamName. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ParamNameProperty =
DependencyProperty.Register("ParamName", typeof(string), typeof(TextParameterControl), new PropertyMetadata(String.Empty));
#endregion
#region value
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(string), typeof(TextParameterControl), new PropertyMetadata(String.Empty));
#endregion
public TextParameterControl()
{
InitializeComponent();
}
}
ParameterListControl.xaml:
<UserControl x:Class="Stuff.ParameterListControl" [..] x:Name="parent">
<UserControl.Resources>
<DataTemplate x:Key="TextParameterTemplate">
<c:TextParameterControl ParamName="{Binding Name}" Value="{Binding Value}"/>
</DataTemplate>
<DataTemplate x:Key="ParameterListTemplate">
<c:ParameterListControl ParamName="{Binding Name}" Value="{Binding Value}" Items="{Binding Parameters}" />
</DataTemplate>
<s:ParameterTemplateSelector x:Key="ParameterSelector"
TextParameterTemplate="{StaticResource TextParameterTemplate}"
ParameterListTemplate="{StaticResource ParameterListTemplate}"/>
</UserControl.Resources>
<Expander DataContext="{Binding ElementName=parent}" Header="{Binding Path=ParamName}" IsExpanded="True" ExpandDirection="Down">
<StackPanel>
<ItemsControl ItemsSource="{Binding Path=Items}" ItemTemplateSelector="{StaticResource ParameterSelector}"></ItemsControl>
</StackPanel>
</Expander>
</UserControl>
ParameterListControl.xaml.cs:
public partial class ParameterListControl: UserControl
{
#region param name
public string ParamName
{
get { return (string)GetValue(ParamNameProperty); }
set { SetValue(ParamNameProperty, value); }
}
// Using a DependencyProperty as the backing store for ParamName. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ParamNameProperty =
DependencyProperty.Register("ParamName", typeof(string), typeof(ParameterListControl), new PropertyMetadata(String.Empty));
#endregion
#region value
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(string), typeof(ParameterListControl), new PropertyMetadata(String.Empty));
#endregion
#region items
public IList<string> Items
{
get { return (List<string>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(IList<string>), typeof(ParameterListControl), new PropertyMetadata(new List<string>()));
#endregion
public ParameterListControl()
{
InitializeComponent();
}
}
Вот мой селектор пользовательского шаблона:
class ParameterTemplateSelector : DataTemplateSelector
{
public DataTemplate ParameterListTemplate { get; set; }
public DataTemplate TextParameterTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is TextParameter)
{
return this.TextParameterTemplate;
}
else if (item is ParameterList)
{
return this.ParameterListTemplate;
}
throw new Exception(String.Format("This parameter ({0}) is not handled in the application", item.GetType().Name));
}
}
А вот вызывающему Просмотр и ViewModel:
ViewModel:
public class MainViewModel : ViewModelBase
{
public ObservableCollection<IParameter> Parameters { get; set; }
public MainViewModel()
{
this.Parameters = new ObservableCollection<IParameter>();
this.Parameters.Add(new TextParameter("Customer"));
// here I am building my complex composite parameter list
}
Просмотреть :
<UserControl.Resources>
<DataTemplate x:Key="TextParameterTemplate">
<c:TextParameterControl ParamName="{Binding Name}" Value="{Binding Value}"/>
</DataTemplate>
<DataTemplate x:Key="ParameterListTemplate">
<c:ParameterListControl ParamName="{Binding Name}" Value="{Binding Value}" Items="{Binding Parameters}" />
</DataTemplate>
<s:ParameterTemplateSelector x:Key="ParameterSelector"
TextParameterTemplate="{StaticResource TextParameterTemplate}"
ParameterListTemplate="{StaticResource ParameterListTemplate}"/>
</UserControl.Resources>
<ItemsControl ItemsSource="{Binding Parameters}" ItemTemplateSelector="{StaticResource ParameterSelector}"></ItemsControl>
При запуске приложения, то TextParameter
в MainViewModel.Parameters
хорошо загружены (VM.Name
и VM.Value
свойства хорошо переплетены с UC.ParamName
и UC.Value
. Противоположно, ParameterList
в MainViewModel.Parameters
частично загружены. UC.Name
хорошо переплетены с UC.ParamName
но VM.Parameters
не переплетены в UC.Items
(в UC.DataContext
является ВМ, то VM.Parameters
хорошо определена, но UC.Items
катастрофически null
).
У вас есть представление о том, что у меня отсутствует? (я не родной, простите меня, если мой английский у тебя болит)
У вас есть проблемы с привязкой и исключение inotifypropertychanged кода? очень плохая идея. также Параметры не показывают никакого измененного кода/инициализатора. также плохая идея – Dbl
Я добавил весь «сантехнический» код. – fharreau
Мне кажется, что проблема по-прежнему заключается в том, что ваше представление пытается привязать свойство, (Параметры) до его установки, и поскольку вы не размещаете вызовы INotifyPropertyChanged в свой свойстве Parameters, WPF не заметит и ваш привязка не может быть решена. – Dbl