WPF TabControl 绑定不允许我更新选项卡数据
WPF TabControl binding doesn't let me update tab data
我的大学作业是使用 WPF 创建一个类似于 Notepad++ 的应用程序。为此,我在 MainWindow.xaml 中创建了一个 TabControl,每个选项卡都有一个 header (TextBlock) 和文本内容 (TextBox)。 TabControl 绑定到名为“Tabs”的 TabModel(模型 class 我用于选项卡项)的 ObservableCollection。
调用“另存为”功能时出现问题。它应该将选项卡内容保存在 .txt 文件中(这工作得很好)并重命名活动选项卡 header 以匹配保存的 .txt 文件的名称(它不会更新 header ).调试显示“选项卡”中的数据已正确更新,但 UI 并未反映出来。
这是里面的代码MainWindow.xaml:
<Window x:Class="Notepad__.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:Notepad__.Commands"
mc:Ignorable="d"
Title="Notepad--"
Name="WindowHeader"
Height="1000"
Width="1200"
WindowStartupLocation="CenterScreen"
Icon="Images\icon.png">
<Window.DataContext>
<local:FileCommands/>
</Window.DataContext>
<Grid>
<Grid.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#E7E9BB" Offset="0.0" />
<GradientStop Color="#403B4A" Offset="1.0" />
</LinearGradientBrush>
</Grid.Background>
<Grid.RowDefinitions>
<RowDefinition Height="25"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Menu>
<Menu.ItemsPanel>
<ItemsPanelTemplate>
<DockPanel HorizontalAlignment="Left"/>
</ItemsPanelTemplate>
</Menu.ItemsPanel>
<MenuItem Header="File" FontSize="14">
<MenuItem Header="New" Command="{Binding Path=CommandNew}"/>
<MenuItem Header="Open..." Command="{Binding Path=CommandOpen}"/>
<MenuItem Header="Save"/>
<MenuItem Header="Save as..." Command="{Binding Path=CommandSaveAs}"/>
<Separator/>
<MenuItem Header="Exit" Command="{Binding Path=CommandExit}"/>
</MenuItem>
<MenuItem Header="Search" FontSize="14">
<MenuItem Header="Find..."/>
<MenuItem Header="Replace..."/>
<MenuItem Header="Replace All..."/>
</MenuItem>
<MenuItem Header="Help" FontSize="14">
<MenuItem Header="About"/>
</MenuItem>
</Menu>
<TabControl
Name = "TabsControl"
ItemsSource="{Binding Tabs, UpdateSourceTrigger=PropertyChanged}"
SelectedIndex="{Binding CurrentTab, UpdateSourceTrigger=PropertyChanged}"
Grid.Row="1"
Margin="25"
FontSize="14">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Filename, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<TextBox
Text="{Binding Content, UpdateSourceTrigger=PropertyChanged}"
TextWrapping="Wrap"
AcceptsReturn="True"
VerticalScrollBarVisibility="Visible"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Grid.Row="1"
Margin="10"
FontSize="26"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
这是 FileCommands.cs 中的代码,我在其中进行了绑定和 SaveAs 函数的实现。我排除了“使用”:
namespace Notepad__.Commands
{
class FileCommands : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<TabModel> Tabs { get; set; }
private int currentTab;
public int CurrentTab { get => currentTab; set => SetProperty(ref currentTab, value); }
private ICommand m_new;
private ICommand m_open;
private ICommand m_saveAs;
public FileCommands()
{
Tabs = new ObservableCollection<TabModel>();
Tabs.Add(new TabModel { Filename = "File 1", Content = "" });
}
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void New(object parameter)
{
Tabs.Add(new TabModel { Filename = "File " + (Tabs.Count + 1), Content = "" });
}
public void Open(object parameter)
{
var openFileDialog = new OpenFileDialog
{
Title = "Select a text file...",
Filter = "Text files (*.txt)|*.txt|All files (*.*)|*.*"
};
if (openFileDialog.ShowDialog() == true)
{
Stream fileStream = openFileDialog.OpenFile();
using (StreamReader reader = new StreamReader(fileStream))
{
Tabs.Add(new TabModel { Filename = openFileDialog.SafeFileName, Content = reader.ReadToEnd() });
OnPropertyChanged("Tabs");
}
}
}
public void SaveAs(object parameter)
{
var saveFileDialog = new SaveFileDialog
{
Title = "Save...",
Filter = "Text files (*.txt)|*.txt|All files (*.*)|*.*"
};
if (saveFileDialog.ShowDialog() == true)
{
File.WriteAllText(saveFileDialog.FileName, Tabs[currentTab].Content);
Tabs[currentTab].Filename = saveFileDialog.SafeFileName;
}
OnPropertyChanged("Tabs");
}
public ICommand CommandNew
{
get
{
if (m_new == null)
m_new = new RelayCommand(New);
return m_new;
}
}
public ICommand CommandOpen
{
get
{
if (m_open == null)
m_open = new RelayCommand(Open);
return m_open;
}
}
public ICommand CommandSaveAs
{
get
{
if (m_saveAs == null)
m_saveAs = new RelayCommand(SaveAs);
return m_saveAs;
}
}
protected bool SetProperty<T>(ref T field, T newValue, [CallerMemberName] string propertyName = null)
{
if (!Equals(field, newValue))
{
field = newValue;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
return true;
}
return false;
}
}
}
我还包含了函数“新建”和“打开”的代码。那是因为它们工作正常,使用 Tabs.Add 添加新选项卡会按预期工作,并且 UI 会相应更新。这就是让我感到困惑的地方:为什么添加标签有效而更新标签无效?
抱歉,如果这个问题很琐碎。这是我第一次使用 C# 工作,也是我的第一个 WPF 项目。我的 class 伙伴和实验室老师都无法帮助我解决问题。感谢阅读,如果我的代码中还有其他内容需要包含,请告诉我:)
如评论中所述,TabModel
需要实现 INotifyPropertyChanged
接口,以便每次更改 属性 值时更新 UI .
您可以创建一个基地 class 只是为了发出通知。
参见下面的示例:
基地:
internal abstract class ObservableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T field, T newValue, [CallerMemberName] string propertyName = null)
{
if (Equals(field, newValue))
return false;
field = newValue;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
return true;
}
}
模型,源自基础:
internal class TabModel : ObservableBase
{
private string _name;
private string _content;
public string Filename
{
get => _name;
set => SetProperty(ref _name, value);
}
public string Content
{
get => _content;
set => SetProperty(ref _content, value);
}
}
虚拟机也一样:
class FileCommands : ObservableBase
{
// ...
}
我的大学作业是使用 WPF 创建一个类似于 Notepad++ 的应用程序。为此,我在 MainWindow.xaml 中创建了一个 TabControl,每个选项卡都有一个 header (TextBlock) 和文本内容 (TextBox)。 TabControl 绑定到名为“Tabs”的 TabModel(模型 class 我用于选项卡项)的 ObservableCollection。
调用“另存为”功能时出现问题。它应该将选项卡内容保存在 .txt 文件中(这工作得很好)并重命名活动选项卡 header 以匹配保存的 .txt 文件的名称(它不会更新 header ).调试显示“选项卡”中的数据已正确更新,但 UI 并未反映出来。
这是里面的代码MainWindow.xaml:
<Window x:Class="Notepad__.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:Notepad__.Commands"
mc:Ignorable="d"
Title="Notepad--"
Name="WindowHeader"
Height="1000"
Width="1200"
WindowStartupLocation="CenterScreen"
Icon="Images\icon.png">
<Window.DataContext>
<local:FileCommands/>
</Window.DataContext>
<Grid>
<Grid.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#E7E9BB" Offset="0.0" />
<GradientStop Color="#403B4A" Offset="1.0" />
</LinearGradientBrush>
</Grid.Background>
<Grid.RowDefinitions>
<RowDefinition Height="25"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Menu>
<Menu.ItemsPanel>
<ItemsPanelTemplate>
<DockPanel HorizontalAlignment="Left"/>
</ItemsPanelTemplate>
</Menu.ItemsPanel>
<MenuItem Header="File" FontSize="14">
<MenuItem Header="New" Command="{Binding Path=CommandNew}"/>
<MenuItem Header="Open..." Command="{Binding Path=CommandOpen}"/>
<MenuItem Header="Save"/>
<MenuItem Header="Save as..." Command="{Binding Path=CommandSaveAs}"/>
<Separator/>
<MenuItem Header="Exit" Command="{Binding Path=CommandExit}"/>
</MenuItem>
<MenuItem Header="Search" FontSize="14">
<MenuItem Header="Find..."/>
<MenuItem Header="Replace..."/>
<MenuItem Header="Replace All..."/>
</MenuItem>
<MenuItem Header="Help" FontSize="14">
<MenuItem Header="About"/>
</MenuItem>
</Menu>
<TabControl
Name = "TabsControl"
ItemsSource="{Binding Tabs, UpdateSourceTrigger=PropertyChanged}"
SelectedIndex="{Binding CurrentTab, UpdateSourceTrigger=PropertyChanged}"
Grid.Row="1"
Margin="25"
FontSize="14">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Filename, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<TextBox
Text="{Binding Content, UpdateSourceTrigger=PropertyChanged}"
TextWrapping="Wrap"
AcceptsReturn="True"
VerticalScrollBarVisibility="Visible"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Grid.Row="1"
Margin="10"
FontSize="26"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
这是 FileCommands.cs 中的代码,我在其中进行了绑定和 SaveAs 函数的实现。我排除了“使用”:
namespace Notepad__.Commands
{
class FileCommands : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<TabModel> Tabs { get; set; }
private int currentTab;
public int CurrentTab { get => currentTab; set => SetProperty(ref currentTab, value); }
private ICommand m_new;
private ICommand m_open;
private ICommand m_saveAs;
public FileCommands()
{
Tabs = new ObservableCollection<TabModel>();
Tabs.Add(new TabModel { Filename = "File 1", Content = "" });
}
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void New(object parameter)
{
Tabs.Add(new TabModel { Filename = "File " + (Tabs.Count + 1), Content = "" });
}
public void Open(object parameter)
{
var openFileDialog = new OpenFileDialog
{
Title = "Select a text file...",
Filter = "Text files (*.txt)|*.txt|All files (*.*)|*.*"
};
if (openFileDialog.ShowDialog() == true)
{
Stream fileStream = openFileDialog.OpenFile();
using (StreamReader reader = new StreamReader(fileStream))
{
Tabs.Add(new TabModel { Filename = openFileDialog.SafeFileName, Content = reader.ReadToEnd() });
OnPropertyChanged("Tabs");
}
}
}
public void SaveAs(object parameter)
{
var saveFileDialog = new SaveFileDialog
{
Title = "Save...",
Filter = "Text files (*.txt)|*.txt|All files (*.*)|*.*"
};
if (saveFileDialog.ShowDialog() == true)
{
File.WriteAllText(saveFileDialog.FileName, Tabs[currentTab].Content);
Tabs[currentTab].Filename = saveFileDialog.SafeFileName;
}
OnPropertyChanged("Tabs");
}
public ICommand CommandNew
{
get
{
if (m_new == null)
m_new = new RelayCommand(New);
return m_new;
}
}
public ICommand CommandOpen
{
get
{
if (m_open == null)
m_open = new RelayCommand(Open);
return m_open;
}
}
public ICommand CommandSaveAs
{
get
{
if (m_saveAs == null)
m_saveAs = new RelayCommand(SaveAs);
return m_saveAs;
}
}
protected bool SetProperty<T>(ref T field, T newValue, [CallerMemberName] string propertyName = null)
{
if (!Equals(field, newValue))
{
field = newValue;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
return true;
}
return false;
}
}
}
我还包含了函数“新建”和“打开”的代码。那是因为它们工作正常,使用 Tabs.Add 添加新选项卡会按预期工作,并且 UI 会相应更新。这就是让我感到困惑的地方:为什么添加标签有效而更新标签无效?
抱歉,如果这个问题很琐碎。这是我第一次使用 C# 工作,也是我的第一个 WPF 项目。我的 class 伙伴和实验室老师都无法帮助我解决问题。感谢阅读,如果我的代码中还有其他内容需要包含,请告诉我:)
如评论中所述,TabModel
需要实现 INotifyPropertyChanged
接口,以便每次更改 属性 值时更新 UI .
您可以创建一个基地 class 只是为了发出通知。
参见下面的示例:
基地:
internal abstract class ObservableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T field, T newValue, [CallerMemberName] string propertyName = null)
{
if (Equals(field, newValue))
return false;
field = newValue;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
return true;
}
}
模型,源自基础:
internal class TabModel : ObservableBase
{
private string _name;
private string _content;
public string Filename
{
get => _name;
set => SetProperty(ref _name, value);
}
public string Content
{
get => _content;
set => SetProperty(ref _content, value);
}
}
虚拟机也一样:
class FileCommands : ObservableBase
{
// ...
}