我正在尝试构建一个允许编辑列表中项目的用户控件。 首先,我将向您展示我的代码和代码下面我将解释我的问题。如果要重现该问题,可以创建一个新项目并将所有代码复制到其中。我将包括所需的每一堂课。
用户控件将绑定到的列表代码:
using System.Collections.Generic;
namespace TestApplicationWPF
{
public class SettingList : List<ISetting>
{
}
}
界面ISetting:
namespace TestApplicationWPF
{
public class ISetting
{
string Name { get; set; }
}
}
我写入列表的通用对象:
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace TestApplicationWPF
{
public class Setting<T> : ISetting, INotifyPropertyChanged
{
public string Name { get; set; }
private T value;
public T Value
{
get
{
return value;
}
set
{
this.value = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
现在,用户控件的目标是绑定到SettingList的一个实例,并允许用户编辑列表中每个“Setting”-instance的“Value”属性。为此,用户控件根据设置中的T类型显示特定控件。例如,String显示在Textbox中,DateTime-Value将显示在DatePicker中。
用户控件的代码如下所示:
<UserControl x:Class="TestApplicationWPF.SettingEditor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:TestApplicationWPF"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<local:TypeOfConverter x:Key="TypeOfConverter" />
<Style x:Key="TypedValueStyle" TargetType="{x:Type ContentControl}">
<Setter Property="Width" Value="200" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="{Binding Path=.}" IsReadOnly="true" Background="LightGray"/>
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={StaticResource TypeOfConverter},Path=Value}"
Value="String">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content, Mode=TwoWay}"
HorizontalAlignment="Stretch"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="80*"/>
<RowDefinition Height="20*"/>
<RowDefinition Height="20*"/>
</Grid.RowDefinitions>
<ListView Name="LvPluginSettings"
ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:SettingEditor}}, Path=Settings, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Row="0">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Label Content="{Binding Path=Name}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Value"
Width="Auto">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ContentControl Content="{Binding Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource TypedValueStyle}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
<Button Name ="BtnSetValue" Grid.Row="1" Content="Set the value" Click="BtnSetValue_Click"/>
<Button Name ="BtnGetValue" Grid.Row="2" Content="What's the value?" Click="BtnGetValue_Click"/>
</Grid>
</UserControl>
用户控件的.cs代码:
using System.Windows;
using System.Windows.Controls;
namespace TestApplicationWPF
{
public partial class SettingEditor : UserControl
{
public SettingList Settings
{
get { return (SettingList)GetValue(PluginSettingsProperty); }
set { SetValue(PluginSettingsProperty, value); }
}
public static readonly DependencyProperty PluginSettingsProperty = DependencyProperty.Register(
"Settings",
typeof(SettingList),
typeof(SettingEditor),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)
);
public SettingEditor()
{
InitializeComponent();
}
private void BtnSetValue_Click(object sender, RoutedEventArgs e)
{
((Setting<string>)Settings[0]).Value = "Different value now.";
}
private void BtnGetValue_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(((Setting<string>)Settings[0]).Value);
}
}
}
为了确定列表中每个项目的“Value”属性的类型,我使用了“TypeOfConverter”。转换器看起来像这样:
using System;
using System.Windows.Data;
namespace TestApplicationWPF
{
public class TypeOfConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (value == null) ? null : value.GetType().Name;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
最后,为了能够完全重现这个问题,我给你使用了用户控件的MainWindow和它的ViewModel: 窗口:
<Window x:Class="TestApplicationWPF.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"
xmlns:local="clr-namespace:TestApplicationWPF"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:MainWindowViewModel x:Key="MainWindowViewModel" />
</Window.Resources>
<Grid DataContext="{StaticResource MainWindowViewModel}">
<local:SettingEditor Settings="{Binding Path=Settings}" />
</Grid>
</Window>
视图模型:
namespace TestApplicationWPF
{
public class MainWindowViewModel
{
public SettingList Settings { get; set; }
public MainWindowViewModel()
{
Settings = new SettingList();
Settings.Add(new Setting<string> { Name="Name of setting", Value = "HelloWorld" });
}
}
}
我的问题是设置实例的Value-Property的绑定。当我启动应用程序时,它将向我显示完美的价值。我得到一个带有“HelloWorld”的文本框。此外,当值在后台更改时,它将更新到文本框。 但是,当我将光标设置到文本框中时,将文本更改为其他内容并保留文本框,它将不会在绑定的“值”-Property中更改。此外,在我尝试编辑文本框中的文本后,在后台进行的更改不再影响文本框。
如果有人能帮助我,我将深表感激。即使是可能出错的最小暗示也会对我有所帮助。
问候, 斯文
答案 0 :(得分:1)
欢迎来到SO!
这是一种利用ObservableCollection
,DataTemplateSelector
和一些C#字幕的方法:D
备注:强>
ObservableCollection<T>
通知订阅者其中的更改UserControl
,而是使用ItemsControl
代替Setting
有目的地抽象,强制执行Setting<T>
用法Setting
个对象等...使用代码并看到它有效地工作而不会很麻烦:D
<强> C#强>
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication4
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
var collection =
new SettingCollection(new Setting[]
{
new Setting<bool> {Name = "boolean"},
new Setting<string> {Name = "string"}
});
DataContext = collection;
}
private void Button1_Click(object sender, RoutedEventArgs e)
{
var collection = (SettingCollection) DataContext;
var setting = collection.OfType<Setting<bool>>().FirstOrDefault();
if (setting != null)
{
setting.Value = !setting.Value;
}
}
private void Button2_Click(object sender, RoutedEventArgs e)
{
var collection = (SettingCollection) DataContext;
var setting = collection.OfType<Setting<string>>().FirstOrDefault();
if (setting != null)
{
setting.Value = DateTime.Now.ToString(CultureInfo.InvariantCulture);
}
}
private void Button3_Click(object sender, RoutedEventArgs e)
{
var collection = (SettingCollection) DataContext;
Debugger.Break();
}
}
public sealed class SettingCollection : ObservableCollection<Setting>
{
public SettingCollection(List<Setting> list) : base(list)
{
}
public SettingCollection(IEnumerable<Setting> collection) : base(collection)
{
}
public SettingCollection()
{
}
}
public abstract class Setting : INotifyPropertyChanged
{
private object _value;
public string Name { get; set; }
public object Value
{
get { return _value; }
set
{
if (Equals(value, _value)) return;
_value = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public sealed class Setting<T> : Setting
{
private T _value;
public new T Value
{
get { return _value; }
set
{
if (Equals(value, _value)) return;
_value = value;
OnPropertyChanged();
}
}
}
public class SettingTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var element = container as FrameworkElement;
if (element != null && item != null)
{
var type = item.GetType();
var types = type.GenericTypeArguments;
var type1 = types[0];
if (type1 == typeof(bool))
{
var findResource = element.FindResource("SettingBoolTemplate");
var dataTemplate = findResource as DataTemplate;
return dataTemplate;
}
if (type1 == typeof(string))
{
var findResource = element.FindResource("SettingStringTemplate");
var dataTemplate = findResource as DataTemplate;
return dataTemplate;
}
}
return null;
}
}
}
<强> XAML:强>
<Window x:Class="WpfApplication4.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"
xmlns:local="clr-namespace:WpfApplication4"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel Margin="20">
<Button Content="Set first Setting<bool> to something" Click="Button1_Click" />
<Button Content="Set first Setting<string> to something" Click="Button2_Click" />
<Button Content="Debugger break" Click="Button3_Click"></Button>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.Resources>
<DataTemplate x:Key="SettingBoolTemplate" DataType="local:Setting">
<CheckBox IsChecked="{Binding Value}" />
</DataTemplate>
<DataTemplate x:Key="SettingStringTemplate" DataType="local:Setting">
<TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
<local:SettingTemplateSelector x:Key="SettingTemplateSelector" />
</ItemsControl.Resources>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="local:Setting">
<Border Margin="5" BorderBrush="DodgerBlue" Padding="2" BorderThickness="1">
<StackPanel>
<TextBlock Text="{Binding Name}" />
<ContentControl Content="{Binding}"
ContentTemplateSelector="{StaticResource SettingTemplateSelector}" />
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</Window>
答案 1 :(得分:1)
我发现了问题。
在用户控件中,我有一个ContentControl,如下所示:
presentation
控件的DataTemplate如下所示:
<ContentControl Content="{Binding Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource TypedValueStyle}" />
问题是数据绑定。 ContentControl的Content绑定到我的对象中的Value-property。但是,DataTemplate绑定到ContentControl的Content-property。为了使绑定工作,我需要特别设置数据模板中控件的绑定到Value-property。
现在正在运行的ContentControl如下所示:
<DataTemplate>
<TextBox Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content, Mode=TwoWay}"
HorizontalAlignment="Stretch"/>
</DataTemplate>
DataTemplate看起来像这样:
<ContentControl DataContext="{Binding}"
Content="{Binding Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource TypedValueStyle}" />