如何从需要动态创建的tabitem自定义控件中获取事件通知?
我在mainWindow.xaml上有按钮。我在这个窗口上也有一个标签控件。我正在根据计算机上通过自定义控件的网络接口卡的数量动态生成选项卡项。 我将每个网卡的数据读入一个单独的标签项,并用MAC,IP,DNS等填写相应的文本框。除了文本框,我还有两个复选框,使用DHCP和自动DNS。如果选项卡项上的ValidationRule失败,我想禁用主窗口上的一个按钮;特别是保存按钮。 我相信,如果用户输入了一些错误数据,我的验证逻辑运行良好,可以尝试禁用保存按钮。我的麻烦是访问自定义控件中的事件,以便我可以对错误做出反应并从主窗口中禁用“保存”按钮。
当我将自定义控件添加到主窗口XAML时,如:
<TabControl x:Name="tabSettings"
Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="5"
Margin="10,10,10,10"
Background="#FF1B1B1B"
ItemsSource="{Binding}"
SelectionChanged="tabSettings_SelectionChanged" />
我不知道如何访问允许我设置属性以禁用“保存”按钮的事件。我确实获得了所有的tabitems和所有正确的数据。
执行类似这样的操作,我将声明TabDataControl类型的自定义控件(应该是标签项):
<tabcntrl:TabDataControl x:Name="tabSettings"
Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="5"
Margin="10,10,10,10"
Background="#FF1B1B1B"
UseDHCP_Click="NICTabs_UseDHCP_Click"
UseDNS_Click="NICTabs_UseDNS_Click" />
让我参加活动,但由于我不知道如何以这种方式创建多个tabitems,我只能显示第一个网卡信息。
将TabDataControl声明为从主窗口访问事件的自定义控件允许我查看我创建的事件作为依赖项属性。但我没有为每个网络接口卡创建一个选项卡。
这里简明一点是创建一些依赖属性的缩写类。
namespace TabDataCustomControl {
public partial class TabDataControl : Control {
private const string PART_USE_DHCP = "PART_useDHCP";
private const string PART_TXT_CURRENTIP = "PART_txtBlkCurrentIP";
private const string PART_TXT_CURRENTSN = "PART_txtBlkCurrentSN";
private const string PART_USE_DNS = "PART_useDNS";
static TabDataControl() {
DefaultStyleKeyProperty.OverrideMetadata( typeof( TabDataControl ),
new FrameworkPropertyMetadata( typeof( TabDataControl ) ) );
}
public override void OnApplyTemplate() {
base.OnApplyTemplate();
UseDHCP = GetTemplateChild( PART_USE_DHCP ) as CheckBox;
UseDNS = GetTemplateChild( PART_USE_DNS ) as CheckBox;
TxtCurrentIP = GetTemplateChild( PART_TXT_CURRENTIP ) as TextBox;
TxtCurrentSN = GetTemplateChild( PART_TXT_CURRENTSN ) as TextBox;
}
CheckBox useDHCP;
protected CheckBox UseDHCP
{
get { return useDHCP; }
set
{
if( useDHCP != null )
{
useDHCP.MouseDown -= useDHCP_MouseDown;
useDHCP.Click -= useDHCP_Click;
}
useDHCP = value;
if( useDHCP != null )
{
useDHCP.MouseDown += useDHCP_MouseDown;
useDHCP.Click += useDHCP_Click;
} } }
void useDHCP_MouseDown( object sender, RoutedEventArgs e )
{ RaiseUseDHCP_MouseDownEvent(); }
protected static readonly RoutedEvent UseDHCP_MouseDownEvent =
EventManager.RegisterRoutedEvent( "UseDHCP_MouseDown", RoutingStrategy.Bubble,
typeof( RoutedEventHandler ), typeof( TabDataControl ) );
public event RoutedEventHandler UseDHCP_MouseDown {
add { AddHandler( UseDHCP_MouseDownEvent, value ); }
remove { RemoveHandler( UseDHCP_MouseDownEvent, value ); }
}
protected virtual void RaiseUseDHCP_MouseDownEvent() {
RoutedEventArgs args = new RoutedEventArgs( TabDataControl.UseDHCP_MouseDownEvent );
RaiseEvent( args );
}
void useDHCP_Click( object sender, RoutedEventArgs e )
{
RaiseUseDHCP_ClickEvent();
}
protected static readonly RoutedEvent UseDHCP_ClickEvent =
EventManager.RegisterRoutedEvent( "UseDHCP_Click", RoutingStrategy.Bubble,
typeof( RoutedEventHandler ), typeof( TabDataControl ) );
public event RoutedEventHandler UseDHCP_Click
{
add { AddHandler( UseDHCP_ClickEvent, value ); }
remove { RemoveHandler( UseDHCP_ClickEvent, value ); }
}
protected virtual void RaiseUseDHCP_ClickEvent()
{
RoutedEventArgs args = new RoutedEventArgs( TabDataControl.UseDHCP_ClickEvent );
RaiseEvent( args );
}
这是mainWindow.xaml.cs中的代码。 createTabs();仅从mainWindow()调用;构造
private void createTabs()
{
foreach ( var nicAdapter in NICAdapters )
{
string newTabeName = nicAdapter.AdapterName;
//Create a new tab and set the data context.
var NIC_Tab = new TabItem()
{
Header = newTabeName,
Content = new TabDataControl(),
DataContext = nicAdapter
};
tabSettings.Items.Add( NIC_Tab );
}
if ( tabSettings != null )
{
Style style = new Style( typeof( TabItem ), Application.Current.FindResource( "tabItemStyle" ) as Style );
tabSettings.Resources.Add( typeof( TabItem ), style );
}
tabSettings.SelectedIndex = 0;
}
答案 0 :(得分:1)
我建议您采取不同的方法,而不是建议您继续沿着点击处理程序和其他噩梦的道路前进。 (当然,这是我们正在讨论的WPF,这意味着你只需要解决一堆不同的问题......哈哈!)
您需要分离关注点。如果你利用WPF提供的一些灵活性(即使它们有点脏,没有一些框架来帮助你),你的生活将会更加容易。
根据您的问题/代码,我假设您对绑定和DataContext
有些熟悉。对于这个窗口,我(粗略地)为它实例化MainViewModel
。标签控件的ItemsSource
设置为Adapters
的{{1}}属性。向此集合添加适配器将在UI上显示该选项卡。
每个适配器的所有属性(MainViewModel
样式的ContentTemplate
)都绑定到适配器对象。
请注意,我们可以在没有任何点击处理程序的情况下启用和禁用IP地址/子网。
TabItem
这些是XAML /视图绑定的对象。
这些使用<Window x:Class="NicApp.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:NicApp"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525" MinWidth="400" MinHeight="500">
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<Window.Resources>
<local:InverseBooleanConverter x:Key="InverseBooleanConverter" />
</Window.Resources>
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TabControl ItemsSource="{Binding Adapters}">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem" BasedOn="{StaticResource {x:Type TabItem}}">
<Setter Property="Header" Value="{Binding Name}" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate DataType="{x:Type local:AdapterViewModel}">
<StackPanel Margin="10">
<CheckBox Content="Use DHCP" IsChecked="{Binding UseDhcp}" Margin="0,0,0,10" />
<Grid Margin="10,0,0,15" IsEnabled="{Binding UseDhcp, Converter={StaticResource InverseBooleanConverter}}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Content="IP Address" Grid.Column="0" Grid.Row="0" Target="{Binding ElementName=IpAddressTextBox}" />
<Label Content="Subnet Mask" Grid.Column="0" Grid.Row="1" Target="{Binding ElementName=SubnetTextBox}" />
<TextBox Text="{Binding IPAddress, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" x:Name="IpAddressTextBox" Grid.Column="1" Grid.Row="0" />
<TextBox Text="{Binding Subnet, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" x:Name="SubnetTextBox" Grid.Column="1" Grid.Row="1" />
</Grid>
<CheckBox Content="Use DNS" IsChecked="{Binding UseDns}" Margin="0,0,0,10" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Grid.Row="1">
<Button Content="OK" Width="100" Margin="0,10,10,0" IsDefault="True" Command="{Binding AcceptCommand}" />
<Button Content="Cancel" Width="100" Margin="0,10,0,0" IsCancel="True" Command="{Binding CancelCommand}" />
</StackPanel>
</Grid>
</Window>
(在Nuget中可用),因为我想使用他们的MVVM Light
(告诉WPF在UI调用INotifyPropertyChanged
时更改UI上的值)和RaisePropertyChanged
(在单击绑定到RelayCommand
的按钮后调用操作,以及禁用未满足执行条件的按钮。)
需要更新构造函数以根据您找到的网络适配器实际填充集合。 accept和cancel命令还需要分别保存更改和取消更改。
RelayCommand
属性不言自明。我还实现了using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf;
using System.Collections.ObjectModel;
using System.Linq;
namespace NicApp
{
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
// TODO actually load adapters
Adapters = new ObservableCollection<AdapterViewModel>();
Adapters.Add(new AdapterViewModel
{
Name = "Ethermet",
IPAddress = "192.168.1.100",
Subnet = "255.255.255.0",
UseDhcp = true,
UseDns = true
});
Adapters.Add(new AdapterViewModel
{
Name = "Wireless",
IPAddress = "192.168.1.101",
Subnet = "255.255.255.0",
UseDhcp = false,
UseDns = false
});
}
private ObservableCollection<AdapterViewModel> _adapters;
public ObservableCollection<AdapterViewModel> Adapters
{
get { return _adapters; }
set { _adapters = value; RaisePropertyChanged(); }
}
private RelayCommand _acceptCommand;
private RelayCommand _cancelCommand;
public RelayCommand AcceptCommand
{
get
{
return _acceptCommand ?? (_acceptCommand = new RelayCommand(() =>
{
// Accept action
}, () => Adapters.All(_ => _.Error == null)));
}
}
public RelayCommand CancelCommand
{
get
{
return _cancelCommand ?? (_cancelCommand = new RelayCommand(() =>
{
// Cancel action
}));
}
}
}
}
来告诉WPF如何验证您的输入。 (可能有一些框架可以让这更容易......我已经使用了IDataErrorInfo
,但这样做太过分了。)
CSLA
我很困,也不想让你挂,所以我只是把这个代码扔到了这里。对不起我的解释不是很好。希望这足以让您了解可以解决问题的路线。