我有一個大學作業,要使用 WPF 創建一個類似于 Notepad 的應用程式。為此,我在 MainWindow.xaml 中創建了一個 TabControl,每個選項卡都有一個標題 (TextBlock) 和文本內容 (TextBox)。TabControl 系結到名為“Tabs”的 TabModel(我用于選項卡項的模型類)的 ObservableCollection。
呼叫“另存為”函式時會出現問題。它應該將選項卡內容保存在 .txt 檔案中(這很好用)并重命名活動選項卡標題以匹配保存的 .txt 檔案的名稱(它不會更新標題)。除錯顯示“選項卡”內的資料已正確更新,但 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>
This is the code inside FileCommands.cs, where I did the binding and the implementation of the SaveAs function. I excluded the "using"s:
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;
}
}
}
I've also included the code for the functions "New" and "Open" as well. That's because they work correctly, adding a new tab using Tabs.Add works as intended and the UI is updated accordingly. This is what confuses me: Why does adding a tab work and updating a tab doesn't?
Sorry if this question is trivial. This is my first time working in C# and my first WPF project. Neither my classmates nor my lab teacher could help me solve my issue. Thanks for reading, if there's anything else from my code that I should include, please let me know :)
uj5u.com熱心網友回復:
正如評論中所討論的,TabModel需要實作INotifyPropertyChanged介面以便在每次更改屬性值時更新 UI。
您可以創建一個僅用于引發通知的基類。
請參見下面的示例:
基地:
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
{
// ...
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/441702.html
標籤:c# wpf data-binding
