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
{
    // ...
}