MasterDetail ListView 和可编辑的 ContentPresenter:怎么了?
MasterDetail ListView and editable ContentPresenter: what is wrong?
我是基于微软官方的示例创建了一个MasterDetail ListView:
MasterDetail ListView UWP sample
我已经根据我的情况对其进行了调整,因为我希望用户可以直接编辑来自 ListViewselect 的项目。但是我遇到了一个奇怪的行为:
- 当我向 ListView 添加一个新项目时,在详细信息容器中完成的当前项目的更改保存得很好
- 但是当我 select ListView 中的现有项目 时,在详细信息容器中完成的当前项目的更改 不是已保存
这是我的应用程序的屏幕截图:
我的ListView的XAML是这样的:
<!-- Master : List of Feedbacks -->
<ListView
x:Name="MasterListViewFeedbacks"
Grid.Row="1"
ItemContainerTransitions="{x:Null}"
ItemTemplate="{StaticResource MasterListViewFeedbacksItemTemplate}"
IsItemClickEnabled="True"
ItemsSource="{Binding CarForm.feedback_comments}"
SelectedItem="{Binding SelectedFeedback, Mode=TwoWay}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.FooterTemplate>
<DataTemplate>
<CommandBar Background="White">
<CommandBar.Content>
<StackPanel Orientation="Horizontal">
<AppBarButton Icon="Add" Label="Add Feedback"
Command="{Binding AddItemFeedbacksCommand}" />
<AppBarButton Icon="Delete" Label="Delete Feedback"
Command="{Binding RemoveItemFeedbacksCommand}" />
</StackPanel>
</CommandBar.Content>
</CommandBar>
</DataTemplate>
</ListView.FooterTemplate>
</ListView>
ListView 的 ItemTemplate 的 XAML 是:
<DataTemplate x:Key="MasterListViewFeedbacksItemTemplate" x:DataType="models:Feedback_Comments">
<StackPanel Margin="0,11,0,13"
Orientation="Horizontal">
<TextBlock Text="{x:Bind creator }"
Style="{ThemeResource BaseTextBlockStyle}" />
<TextBlock Text=" - " />
<TextBlock Text="{x:Bind comment_date }"
Margin="12,1,0,0" />
</StackPanel>
</DataTemplate>
Details容器的XAML是这样的:
<!-- Detail : Selected Feedback -->
<ContentPresenter
x:Name="DetailFeedbackContentPresenter"
Grid.Column="1"
Grid.RowSpan="2"
BorderThickness="1,0,0,0"
Padding="24,0"
BorderBrush="{ThemeResource SystemControlForegroundBaseLowBrush}"
Content="{x:Bind MasterListViewFeedbacks.SelectedItem, Mode=OneWay}">
<ContentPresenter.ContentTemplate>
<DataTemplate x:DataType="models:Feedback_Comments">
<StackPanel Visibility="{Binding FeedbacksCnt, Converter={StaticResource CountToVisibilityConverter}}">
<TextBox Text="{Binding creator, Mode=TwoWay}" />
<DatePicker Date="{Binding comment_date, Converter={StaticResource DateTimeToDateTimeOffsetConverter}, Mode=TwoWay}"/>
<TextBox TextWrapping="Wrap" AcceptsReturn="True" IsSpellCheckEnabled="True"
Text="{Binding comment, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</ContentPresenter.ContentTemplate>
<ContentPresenter.ContentTransitions>
<!-- Empty by default. See MasterListView_ItemClick -->
<TransitionCollection />
</ContentPresenter.ContentTransitions>
</ContentPresenter>
“CarForm”是我的 ViewModel 的主要对象。每个 CarForm 包含一个列表“Feedback_Comments”。
所以在我的 ViewModel 中,我在添加 新评论 时这样做:
private void AddItemFeedbacks()
{
FeedbacksCnt++;
CarForm.feedback_comments.Add(new Feedback_Comments()
{
sequence = FeedbacksCnt,
creator_id = user_id,
_creator = username,
comment_date = DateTime.Now
});
SelectedFeedback = CarForm.feedback_comments[CarForm.feedback_comments.Count - 1];
}
=> 添加前编辑的Feedback_Comment中所做的修改得到很好的保存
当用户 select 现有 Feedback_Comment 时,我什么都不做:这是由 XAML 直接管理的。
=> 之前编辑的 Feedback_Comment 中对 select 另一个所做的更改未保留
=> 你有什么解释吗?
Text
属性 的 TwoWay
绑定仅在 TextBox
失去焦点时更新。但是,当您 select 列表中的不同项目时,TextBox
的内容不再绑定到原始项目,因此不会更新。
要在每次 Text
内容更改时触发更新,以便立即反映更改,请将 UpdateSourceTrigger
设置为 PropertyChanged
:
<TextBox Text="{Binding comment, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
到处触发变化
为确保您的更改在包括列表在内的任何地方都得到反映,您需要做两件事。
首先,您的 feedback_comments
是 ObservableCollection<Feedback_Comments>
类型。这确保添加和删除的项目从 ListView
.
中添加和删除
其次,Feedback_Comments
class 必须实现 INotifyPropertyChanged
接口。需要此接口让用户界面了解数据绑定对象属性的更改。
实现此接口相当简单,并在 for example on MSDN.
中进行了描述
快速解决方案如下所示:
public class Feedback_Comments : INotifyPropertyChanged
{
// your code
//INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged( [ CallerMemberName ]string propertyName = "" )
{
PropertyChanged?.Invoke( this, new PropertyChangedEventArgs( propertyName ) );
}
}
现在每个 属性 setter 在设置值后调用 OnPropertyChanged();
:
private string _comment = "";
public string Comment
{
get
{
return _comment;
}
set
{
_comment = value;
OnPropertyChanged();
}
}
请注意,[CallerMemberName]
属性告诉编译器将参数替换为调用者的名称 - 在本例中为 属性 的名称,这正是您所需要的。
另请注意,在这种情况下您不能使用简单的自动属性(因为您需要调用 OnPropertyChanged
方法。
奖金
最后,作为一个小建议,我看到您正在使用类似 C++ 的命名约定,这不太适合 C# 世界。看看 recommended C# naming conventions 以提高代码可读性:-) .
我是基于微软官方的示例创建了一个MasterDetail ListView: MasterDetail ListView UWP sample
我已经根据我的情况对其进行了调整,因为我希望用户可以直接编辑来自 ListViewselect 的项目。但是我遇到了一个奇怪的行为:
- 当我向 ListView 添加一个新项目时,在详细信息容器中完成的当前项目的更改保存得很好
- 但是当我 select ListView 中的现有项目 时,在详细信息容器中完成的当前项目的更改 不是已保存
这是我的应用程序的屏幕截图:
我的ListView的XAML是这样的:
<!-- Master : List of Feedbacks -->
<ListView
x:Name="MasterListViewFeedbacks"
Grid.Row="1"
ItemContainerTransitions="{x:Null}"
ItemTemplate="{StaticResource MasterListViewFeedbacksItemTemplate}"
IsItemClickEnabled="True"
ItemsSource="{Binding CarForm.feedback_comments}"
SelectedItem="{Binding SelectedFeedback, Mode=TwoWay}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.FooterTemplate>
<DataTemplate>
<CommandBar Background="White">
<CommandBar.Content>
<StackPanel Orientation="Horizontal">
<AppBarButton Icon="Add" Label="Add Feedback"
Command="{Binding AddItemFeedbacksCommand}" />
<AppBarButton Icon="Delete" Label="Delete Feedback"
Command="{Binding RemoveItemFeedbacksCommand}" />
</StackPanel>
</CommandBar.Content>
</CommandBar>
</DataTemplate>
</ListView.FooterTemplate>
</ListView>
ListView 的 ItemTemplate 的 XAML 是:
<DataTemplate x:Key="MasterListViewFeedbacksItemTemplate" x:DataType="models:Feedback_Comments">
<StackPanel Margin="0,11,0,13"
Orientation="Horizontal">
<TextBlock Text="{x:Bind creator }"
Style="{ThemeResource BaseTextBlockStyle}" />
<TextBlock Text=" - " />
<TextBlock Text="{x:Bind comment_date }"
Margin="12,1,0,0" />
</StackPanel>
</DataTemplate>
Details容器的XAML是这样的:
<!-- Detail : Selected Feedback -->
<ContentPresenter
x:Name="DetailFeedbackContentPresenter"
Grid.Column="1"
Grid.RowSpan="2"
BorderThickness="1,0,0,0"
Padding="24,0"
BorderBrush="{ThemeResource SystemControlForegroundBaseLowBrush}"
Content="{x:Bind MasterListViewFeedbacks.SelectedItem, Mode=OneWay}">
<ContentPresenter.ContentTemplate>
<DataTemplate x:DataType="models:Feedback_Comments">
<StackPanel Visibility="{Binding FeedbacksCnt, Converter={StaticResource CountToVisibilityConverter}}">
<TextBox Text="{Binding creator, Mode=TwoWay}" />
<DatePicker Date="{Binding comment_date, Converter={StaticResource DateTimeToDateTimeOffsetConverter}, Mode=TwoWay}"/>
<TextBox TextWrapping="Wrap" AcceptsReturn="True" IsSpellCheckEnabled="True"
Text="{Binding comment, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</ContentPresenter.ContentTemplate>
<ContentPresenter.ContentTransitions>
<!-- Empty by default. See MasterListView_ItemClick -->
<TransitionCollection />
</ContentPresenter.ContentTransitions>
</ContentPresenter>
“CarForm”是我的 ViewModel 的主要对象。每个 CarForm 包含一个列表“Feedback_Comments”。
所以在我的 ViewModel 中,我在添加 新评论 时这样做:
private void AddItemFeedbacks()
{
FeedbacksCnt++;
CarForm.feedback_comments.Add(new Feedback_Comments()
{
sequence = FeedbacksCnt,
creator_id = user_id,
_creator = username,
comment_date = DateTime.Now
});
SelectedFeedback = CarForm.feedback_comments[CarForm.feedback_comments.Count - 1];
}
=> 添加前编辑的Feedback_Comment中所做的修改得到很好的保存
当用户 select 现有 Feedback_Comment 时,我什么都不做:这是由 XAML 直接管理的。
=> 之前编辑的 Feedback_Comment 中对 select 另一个所做的更改未保留
=> 你有什么解释吗?
Text
属性 的 TwoWay
绑定仅在 TextBox
失去焦点时更新。但是,当您 select 列表中的不同项目时,TextBox
的内容不再绑定到原始项目,因此不会更新。
要在每次 Text
内容更改时触发更新,以便立即反映更改,请将 UpdateSourceTrigger
设置为 PropertyChanged
:
<TextBox Text="{Binding comment, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
到处触发变化
为确保您的更改在包括列表在内的任何地方都得到反映,您需要做两件事。
首先,您的 feedback_comments
是 ObservableCollection<Feedback_Comments>
类型。这确保添加和删除的项目从 ListView
.
其次,Feedback_Comments
class 必须实现 INotifyPropertyChanged
接口。需要此接口让用户界面了解数据绑定对象属性的更改。
实现此接口相当简单,并在 for example on MSDN.
中进行了描述快速解决方案如下所示:
public class Feedback_Comments : INotifyPropertyChanged
{
// your code
//INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged( [ CallerMemberName ]string propertyName = "" )
{
PropertyChanged?.Invoke( this, new PropertyChangedEventArgs( propertyName ) );
}
}
现在每个 属性 setter 在设置值后调用 OnPropertyChanged();
:
private string _comment = "";
public string Comment
{
get
{
return _comment;
}
set
{
_comment = value;
OnPropertyChanged();
}
}
请注意,[CallerMemberName]
属性告诉编译器将参数替换为调用者的名称 - 在本例中为 属性 的名称,这正是您所需要的。
另请注意,在这种情况下您不能使用简单的自动属性(因为您需要调用 OnPropertyChanged
方法。
奖金
最后,作为一个小建议,我看到您正在使用类似 C++ 的命名约定,这不太适合 C# 世界。看看 recommended C# naming conventions 以提高代码可读性:-) .