带可关闭 TabItem 的 TabControl Header
TabControl with Closable TabItem Header
我正在尝试创建 TabItem Headers,其中包含允许用户关闭选项卡的按钮。 object 的视觉表示和数据绑定很好。
我已经尝试过 DataContext,但到目前为止我还没有找到可行的解决方案。
我的XAML:
<TabControl
Grid.Column="3"
Grid.Row="2"
x:Name="TabControlTargets"
ItemsSource="{Binding Path=ViewModelTarget.IpcConfig.DatabasesList, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding Path=ViewModelTarget.SelectedTab, UpdateSourceTrigger=PropertyChanged}">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<TextBlock FontFamily="Calibri" FontSize="15" FontWeight="Bold" Foreground="{Binding FontColor}" Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Center" Margin="0,0,20,0"/>
<Button HorizontalAlignment="Left" DataContext="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext}" Command="{Binding Path = ViewModelTarget.buttonRemoveDatabaseCommand}"
CommandParameter="**?**"
>
<Button.Content>
<Image Height="15" Width="15" Source="pack://application:,,,/Images/cancel.png" />
</Button.Content>
</Button>
</StackPanel>
</DataTemplate>
我无法弄清楚如何设置按钮的 CommandParameter 以使其指向正确的 object。
这是我的 RelayCommand:
public ICommand buttonRemoveDatabaseCommand
{
get
{
if (_buttonRemoveDatabaseCommand == null)
{
_buttonRemoveDatabaseCommand = new RelayCommand(
param => RemoveDatabase(param)
);
}
return _buttonRemoveDatabaseCommand;
}
}
这是我的 RemoveDatabase 函数:
public void RemoveDatabase(object dB)
{
this.IpcConfig.RemoveDataBase((PCDatabase)dB);
}
我强烈希望采用符合我的 "no code behind" 方法的解决方案。
正如评论中所指出的,您可以使用 CommandParameter="{Binding}"
将 TabItem
上下文传递给命令。
更好的方法是将命令移动到 TabItem
的 ViewModel。
这里是使用 Prism 和 Prism 的 EventAggregator
的示例实现。您当然可以使用所有其他 MVVM 框架来实现它,甚至可以自己实现它,但这取决于您。
这将是您的 TabControl
ViewModel,其中包含所有数据库的列表或它要表示的任何内容。
public class DatabasesViewModel : BindableBase
{
private readonly IEventAggregator eventAggregator;
public ObservableCollection<DatabaseViewModel> Databases { get; private set; }
public CompositeCommand CloseAllCommand { get; }
public DatabasesViewModel(IEventAggregator eventAggregator)
{
if (eventAggregator == null)
throw new ArgumentNullException(nameof(eventAggregator));
this.eventAggregator = eventAggregator;
// Composite Command to close all tabs at once
CloseAllCommand = new CompositeCommand();
Databases = new ObservableCollection<DatabaseViewModel>();
// Add a sample object to the collection
AddDatabase(new PcDatabase());
// Register to the CloseDatabaseEvent, which will be fired from the child ViewModels on close
this.eventAggregator
.GetEvent<CloseDatabaseEvent>()
.Subscribe(OnDatabaseClose);
}
private void AddDatabase(PcDatabase db)
{
// In reallity use the factory pattern to resolve the depencency of the ViewModel and assing the
// database to it
var viewModel = new DatabaseViewModel(eventAggregator)
{
Database = db
};
// Register to the close command of all TabItem ViewModels, so we can close then all with a single command
CloseAllCommand.RegisterCommand(viewModel.CloseCommand);
Databases.Add(viewModel);
}
// Called when the event is received
private void OnDatabaseClose(DatabaseViewModel databaseViewModel)
{
Databases.Remove(databaseViewModel);
}
}
每个选项卡都会得到一个 DatabaseViewModel
作为上下文。这是定义关闭命令的地方。
public class DatabaseViewModel : BindableBase
{
private readonly IEventAggregator eventAggregator;
public DatabaseViewModel(IEventAggregator eventAggregator)
{
if (eventAggregator == null)
throw new ArgumentNullException(nameof(eventAggregator));
this.eventAggregator = eventAggregator;
CloseCommand = new DelegateCommand(Close);
}
public PcDatabase Database { get; set; }
public ICommand CloseCommand { get; }
private void Close()
{
// Send a refence to ourself
eventAggregator
.GetEvent<CloseDatabaseEvent>()
.Publish(this);
}
}
当您单击 TabItem
上的关闭按钮时,将调用 CloseCommand
并发送一个事件,通知所有订阅者该选项卡应该关闭。在上面的示例中,DatabasesViewModel
侦听此事件并将接收它,然后可以将其从 ObservableCollection<DatabaseViewModel>
集合中删除。
为了使这种方式的优势更加明显,我添加了一个CloseAllCommand
,这是一个CompositeCommand
,它在添加时注册到每个DatabaseViewModel
s CloseCommand
到 Databases
可观察集合,调用时将调用所有已注册的命令。
CloseDatabaseEvent
是一个非常简单的标记,它决定了它接收到的负载类型,在本例中是 DatabaseViewModel
。
public class CloseDatabaseEvent : PubSubEvent<DatabaseViewModel> { }
在实际应用程序中,您希望避免使用 ViewModel(此处 DatabaseViewModel
)作为有效负载,因为这会导致紧密耦合,因此应避免使用事件聚合器模式。
在这种情况下,它可以被认为是可以接受的,因为 DatabasesViewModel
需要了解 DatabaseViewModel
,但如果可能,最好使用 ID(Guid、int、string)。
这样做的好处是,您还可以通过其他方式(即菜单、功能区或上下文菜单)关闭选项卡,其中您可能没有对 DatabasesViewModel
数据上下文的引用。
我正在尝试创建 TabItem Headers,其中包含允许用户关闭选项卡的按钮。 object 的视觉表示和数据绑定很好。
我已经尝试过 DataContext,但到目前为止我还没有找到可行的解决方案。
我的XAML:
<TabControl
Grid.Column="3"
Grid.Row="2"
x:Name="TabControlTargets"
ItemsSource="{Binding Path=ViewModelTarget.IpcConfig.DatabasesList, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding Path=ViewModelTarget.SelectedTab, UpdateSourceTrigger=PropertyChanged}">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<TextBlock FontFamily="Calibri" FontSize="15" FontWeight="Bold" Foreground="{Binding FontColor}" Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Center" Margin="0,0,20,0"/>
<Button HorizontalAlignment="Left" DataContext="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext}" Command="{Binding Path = ViewModelTarget.buttonRemoveDatabaseCommand}"
CommandParameter="**?**"
>
<Button.Content>
<Image Height="15" Width="15" Source="pack://application:,,,/Images/cancel.png" />
</Button.Content>
</Button>
</StackPanel>
</DataTemplate>
我无法弄清楚如何设置按钮的 CommandParameter 以使其指向正确的 object。
这是我的 RelayCommand:
public ICommand buttonRemoveDatabaseCommand
{
get
{
if (_buttonRemoveDatabaseCommand == null)
{
_buttonRemoveDatabaseCommand = new RelayCommand(
param => RemoveDatabase(param)
);
}
return _buttonRemoveDatabaseCommand;
}
}
这是我的 RemoveDatabase 函数:
public void RemoveDatabase(object dB)
{
this.IpcConfig.RemoveDataBase((PCDatabase)dB);
}
我强烈希望采用符合我的 "no code behind" 方法的解决方案。
正如评论中所指出的,您可以使用 CommandParameter="{Binding}"
将 TabItem
上下文传递给命令。
更好的方法是将命令移动到 TabItem
的 ViewModel。
这里是使用 Prism 和 Prism 的 EventAggregator
的示例实现。您当然可以使用所有其他 MVVM 框架来实现它,甚至可以自己实现它,但这取决于您。
这将是您的 TabControl
ViewModel,其中包含所有数据库的列表或它要表示的任何内容。
public class DatabasesViewModel : BindableBase
{
private readonly IEventAggregator eventAggregator;
public ObservableCollection<DatabaseViewModel> Databases { get; private set; }
public CompositeCommand CloseAllCommand { get; }
public DatabasesViewModel(IEventAggregator eventAggregator)
{
if (eventAggregator == null)
throw new ArgumentNullException(nameof(eventAggregator));
this.eventAggregator = eventAggregator;
// Composite Command to close all tabs at once
CloseAllCommand = new CompositeCommand();
Databases = new ObservableCollection<DatabaseViewModel>();
// Add a sample object to the collection
AddDatabase(new PcDatabase());
// Register to the CloseDatabaseEvent, which will be fired from the child ViewModels on close
this.eventAggregator
.GetEvent<CloseDatabaseEvent>()
.Subscribe(OnDatabaseClose);
}
private void AddDatabase(PcDatabase db)
{
// In reallity use the factory pattern to resolve the depencency of the ViewModel and assing the
// database to it
var viewModel = new DatabaseViewModel(eventAggregator)
{
Database = db
};
// Register to the close command of all TabItem ViewModels, so we can close then all with a single command
CloseAllCommand.RegisterCommand(viewModel.CloseCommand);
Databases.Add(viewModel);
}
// Called when the event is received
private void OnDatabaseClose(DatabaseViewModel databaseViewModel)
{
Databases.Remove(databaseViewModel);
}
}
每个选项卡都会得到一个 DatabaseViewModel
作为上下文。这是定义关闭命令的地方。
public class DatabaseViewModel : BindableBase
{
private readonly IEventAggregator eventAggregator;
public DatabaseViewModel(IEventAggregator eventAggregator)
{
if (eventAggregator == null)
throw new ArgumentNullException(nameof(eventAggregator));
this.eventAggregator = eventAggregator;
CloseCommand = new DelegateCommand(Close);
}
public PcDatabase Database { get; set; }
public ICommand CloseCommand { get; }
private void Close()
{
// Send a refence to ourself
eventAggregator
.GetEvent<CloseDatabaseEvent>()
.Publish(this);
}
}
当您单击 TabItem
上的关闭按钮时,将调用 CloseCommand
并发送一个事件,通知所有订阅者该选项卡应该关闭。在上面的示例中,DatabasesViewModel
侦听此事件并将接收它,然后可以将其从 ObservableCollection<DatabaseViewModel>
集合中删除。
为了使这种方式的优势更加明显,我添加了一个CloseAllCommand
,这是一个CompositeCommand
,它在添加时注册到每个DatabaseViewModel
s CloseCommand
到 Databases
可观察集合,调用时将调用所有已注册的命令。
CloseDatabaseEvent
是一个非常简单的标记,它决定了它接收到的负载类型,在本例中是 DatabaseViewModel
。
public class CloseDatabaseEvent : PubSubEvent<DatabaseViewModel> { }
在实际应用程序中,您希望避免使用 ViewModel(此处 DatabaseViewModel
)作为有效负载,因为这会导致紧密耦合,因此应避免使用事件聚合器模式。
在这种情况下,它可以被认为是可以接受的,因为 DatabasesViewModel
需要了解 DatabaseViewModel
,但如果可能,最好使用 ID(Guid、int、string)。
这样做的好处是,您还可以通过其他方式(即菜单、功能区或上下文菜单)关闭选项卡,其中您可能没有对 DatabasesViewModel
数据上下文的引用。