如果您打算使您的Level 项目可编辑,您必须实现INotifyPropertyChanged。我为演示目的创建了一个关卡视图模型,并添加了一个属性OverallElevation,它表示当前的标高,包括以前的标高。
public class LevelViewModel : INotifyPropertyChanged
{
private string _name;
private int _number;
private double _elevation;
private double _overallElevation;
public LevelViewModel(string name, int number, double elevation, double overallElevation)
{
Number = number;
Name = name;
Elevation = elevation;
OverallElevation = overallElevation;
}
public string Name
{
get => _name;
set
{
if (_name == value)
return;
_name = value;
OnPropertyChanged();
}
}
public int Number
{
get => _number;
set
{
if (_number == value)
return;
_number = value;
OnPropertyChanged();
}
}
public double Elevation
{
get => _elevation;
set
{
if (_elevation.CompareTo(value) == 0)
return;
_elevation = value;
OnPropertyChanged();
}
}
public double OverallElevation
{
get => _overallElevation;
set
{
if (_overallElevation.CompareTo(value) == 0)
return;
_overallElevation = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
您可以将这些属性绑定到您的LevelDefinition 用户控件。我改编了你的样本,因为它不完整。由于计算了整体高程,因此我将相应的TextBox 设置为只读,但您应该真正使用TextBlock 或类似的只读控件。
<UserControl x:Class="RevitPrototype.Setup.LevelDefinition"
...>
<UserControl.Resources>
<Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource {x:Type TextBox}}">
<Setter Property="Margin" Value="5"/>
</Style>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="Level:"/>
<TextBox Grid.Column="1" Name="LevelName" Text="{Binding Name}"/>
<TextBox Grid.Column="2" Name="LevelElevation" Text="{Binding OverallElevation}" IsReadOnly="True"/>
<TextBox Grid.Column="3" Name="ToFloorAbove" Text="{Binding Elevation}"/>
</Grid>
</UserControl>
由于您没有提供您的选项卡视图模型,我创建了一个以供参考。此视图模型公开了一个ObservableCollection 级别、一个GroundFloor 属性以及添加和删除级别的命令。我使用DelegateCommand 类型,但您可以使用其他类型。
每次添加关卡时,您都会订阅新关卡的PropertyChanged 事件,而在移除关卡时,您会取消订阅以防止内存泄漏。现在,每当LevelViewModel 实例上的属性发生更改时,都会调用OnLevelPropertyChanged 方法。此方法检查 Elevation 属性是否已更改。如果是,则调用UpdateOverallElevation 方法,该方法重新计算所有整体高程属性。当然,您可以对此进行优化,仅重新计算高于当前作为sender 传递的级别。
为了更健壮的实现,你应该订阅Levels集合的CollectionChanged event,这样你就可以订阅和取消订阅关卡项目的PropertyChanged事件,只要你添加、删除或修改其他集合方式而不是通过诸如恢复持久集合之类的命令。
public class LevelsViewModel
{
private const string GroundName = "GROUND FLOOR";
private const string LevelName = "LEVEL";
public ObservableCollection<LevelViewModel> Levels { get; }
public LevelViewModel GroundFloor { get; }
public ICommand Add { get; }
public ICommand Remove { get; }
public LevelsViewModel()
{
Levels = new ObservableCollection<LevelViewModel>();
GroundFloor = new LevelViewModel(GroundName, 0, 0, 0);
Add = new DelegateCommand<string>(ExecuteAdd);
Remove = new DelegateCommand(ExecuteRemove);
GroundFloor.PropertyChanged += OnLevelPropertyChanged;
}
private void ExecuteAdd(string arg)
{
if (!double.TryParse(arg, out var value))
return;
var lastLevel = Levels.Any() ? Levels.Last() : GroundFloor;
var number = lastLevel.Number + 1;
var name = GetDefaultLevelName(number);
var overallHeight = lastLevel.OverallElevation + value;
var level = new LevelViewModel(name, number, value, overallHeight);
level.PropertyChanged += OnLevelPropertyChanged;
Levels.Add(level);
}
private void ExecuteRemove()
{
if (!Levels.Any())
return;
var lastLevel = Levels.Last();
lastLevel.PropertyChanged -= OnLevelPropertyChanged;
Levels.Remove(lastLevel);
}
private void OnLevelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName != nameof(LevelViewModel.Elevation))
return;
UpdateOverallElevation();
}
private static string GetDefaultLevelName(int number)
{
return $"{LevelName} {number:D2}";
}
private void UpdateOverallElevation()
{
GroundFloor.OverallElevation = GroundFloor.Elevation;
var previousLevel = GroundFloor;
foreach (var level in Levels)
{
level.OverallElevation = previousLevel.OverallElevation + level.Elevation;
previousLevel = level;
}
}
}
级别选项卡项目的视图可能如下所示。您可以使用带有LevelDefinition 用户控件的ListBox 作为项目模板来显示级别。或者,您可以为LevelViewModel 的每个属性使用带有可编辑列的DataGrid,这对用户来说会更加灵活。
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListView ItemsSource="{Binding Levels}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<local:LevelDefinition/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListView>
<DockPanel Grid.Row="1" Margin="5">
<Button DockPanel.Dock="Right" Content="-" MinWidth="50" Command="{Binding Remove}"/>
<Button DockPanel.Dock="Right" Content="+" MinWidth="50" Command="{Binding Add}" CommandParameter="{Binding Text, ElementName=NewLevelElevationTextBox}"/>
<TextBox x:Name="NewLevelElevationTextBox" MinWidth="100"/>
</DockPanel>
<local:LevelDefinition Grid.Row="2" DataContext="{Binding GroundFloor}"/>
</Grid>
这是一个简化的例子,没有输入验证,添加时忽略无效值。