优化界面样式与功能,增强多语言支持

在 `App.xaml` 中添加了对 `MCListViewStyle.xaml` 的引用,并格式化了 XAML 语法。
在 `MCI18n.csproj` 中移除了不必要的文件夹引用。
修改了 `MainWindow.xaml` 的背景颜色,添加了打包按钮及章节任务显示逻辑。
在 `MainWindow.xaml.cs` 中实现了章节选择变化的处理逻辑。
更新了 `MainWindowViewModel.cs`,增强了语言支持和任务加载逻辑。
新增了 `ChapterModel`、`QuestLangModel`、`QuestModel` 和 `TaskModel` 类。
更新了 `MCButtonStyle.xaml` 和 `MCListViewStyle.xaml` 的样式。
在 `GlobalContext.cs` 中添加了多语言支持设置。
对 `einformation_7new_mechanics.snbt` 文件进行了结构化和格式化。
This commit is contained in:
Ling 2025-06-22 21:09:32 +08:00
parent 33a7ac5be3
commit 6edc4897fa
14 changed files with 461 additions and 4851 deletions

View File

@ -1,16 +1,18 @@
<Application x:Class="MCI18n.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MCI18n"
StartupUri="MainWindow.xaml">
<Application
x:Class="MCI18n.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MCI18n"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- 引入自定义按钮样式 -->
<ResourceDictionary Source="Theme/MCButtonStyle.xaml"/>
<ResourceDictionary Source="Theme/MCTabTheme.xaml"/>
<!-- 引入自定义按钮样式 -->
<ResourceDictionary Source="Theme/MCButtonStyle.xaml" />
<ResourceDictionary Source="Theme/MCTabTheme.xaml" />
<ResourceDictionary Source="Theme/MCListViewStyle.xaml" />
</ResourceDictionary.MergedDictionaries>
<local:NullToVisibilityConverter x:Key="NullToVisibilityConverter"/>
<local:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@ -21,20 +21,10 @@
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
</ItemGroup>
<ItemGroup>
<None Update="einformation_7new_mechanics.snbt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SharpNBT\SharpNBT\SharpNBT.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Models\" />
</ItemGroup>
</Project>

View File

@ -8,7 +8,7 @@
Title="SNBT编辑器"
Width="900"
Height="600"
Background="#3d3d3d"
Background="#1f1f1f"
Foreground="White"
mc:Ignorable="d">
@ -32,24 +32,83 @@
Width="100"
Height="30"
Command="{Binding LoadQuestsCommand}"
Content="打开文件夹"
Style="{StaticResource MinecraftButtonStyle}" />
Content="打开文件夹" />
<Button
Width="100"
Height="30"
Command="{Binding SaveQuestAsCommand}"
Content="另存为"
Style="{StaticResource MinecraftButtonStyle}" />
Content="另存为" />
<Button
Width="100"
Height="30"
Command="{Binding SaveQuestCommand}"
Content="保存(覆盖)"
Style="{StaticResource MinecraftButtonStyle}" />
Content="保存(覆盖)" />
<Button
Width="100"
Height="30"
Command="{Binding SaveQuestCommand}"
Content="打包"
IsEnabled="False" />
</StackPanel>
<TabControl Grid.Row="1">
<TabItem Header="任务列表" />
<TabItem Header="任务列表">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListView
Grid.Column="0"
ItemsSource="{Binding Chapters}"
SelectionChanged="ChapterListView_SelectionChanged">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock
Margin="5"
FontSize="12"
Foreground="White">
<Run Text="{Binding Title}" />
</TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ScrollViewer Grid.Column="1">
<ItemsControl ItemsSource="{Binding SelectedQuests}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<GroupBox>
<GroupBox.Header>
<StackPanel Orientation="Vertical">
<TextBox
Background="Transparent"
BorderThickness="0"
FontSize="12"
Foreground="White"
Text="{Binding Title}" />
<TextBox
Background="Transparent"
BorderThickness="0"
FontSize="12"
Foreground="LightGray"
Text="{Binding SubTitle}" />
</StackPanel>
</GroupBox.Header>
<StackPanel>
<TextBlock Text="描述:" />
<TextBox
AcceptsReturn="True"
Text="{Binding Description}"
TextWrapping="Wrap" />
</StackPanel>
</GroupBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</TabItem>
<TabItem Header="战利品列表" />
</TabControl>
<!-- 状态栏

View File

@ -1,4 +1,5 @@


using MCI18n.Models;
using MCI18n.Utilities;
using System.Diagnostics;
using System.Text;
@ -25,6 +26,14 @@ namespace MCI18n
InitializeComponent();
this.DataContext = viewModel = new MainWindowViewModel();
}
private void ChapterListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if(sender is ListView listView)
{
var selectItem = listView.SelectedItem;
viewModel.ChapterSelectionChanged(selectItem as ChapterModel);
}
}
}
}

View File

@ -1,6 +1,7 @@

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Input;
using MCI18n.Models;
using MCI18n.Utilities;
using Microsoft.Win32;
using SharpNBT;
@ -18,48 +19,160 @@ using System.Windows;
namespace MCI18n
{
public partial class MainWindowViewModel : ObservableObject
{
{
private List<CompoundTag> _oriQuestsData;
private List<string> _updatedChapters;
private List<QuestLangModel> _langs;
[ObservableProperty]
private ObservableCollection<ChapterModel> _chapters;
[ObservableProperty]
private string _currentFilePath;
private ObservableCollection<QuestModel> _selectedQuests;
[ObservableProperty]
private bool _loading = false;
[RelayCommand]
private void LoadQuests()
{
var openFileDialog = new OpenFileDialog
{
Filter = "SNBT Files (*.snbt)|*.snbt|All Files (*.*)|*.*",
Title = "选择SNBT文件"
var openFolderDialog = new OpenFolderDialog
{
Title = "选择ftbquests文件夹",
Multiselect = false
};
if (openFileDialog.ShowDialog() == true)
if (openFolderDialog.ShowDialog() == true)
{
try
{
CurrentFilePath = openFileDialog.FileName;
//var questData = SNBTParser.LoadFromFile<QuestData>(CurrentFilePath);
var questData = StringNbt.Parse(File.ReadAllText(CurrentFilePath));
//if (questData != null)
//{
// //QuestList = new ObservableCollection<QuestData> { questData };
// //SelectedQuest = questData;
//}
//else
//{
// //QuestList = new ObservableCollection<QuestData>();
// //SelectedQuest = null;
//}
//var fileContent = File.ReadAllText(CurrentFilePath, Encoding.UTF8);
//var parser = new NbtTagParser();
//var snbtData = parser.Parse(fileContent);
// var datas = SNBTParser.LoadFromFile(CurrentFilePath).Parse();
{
var folderPath = openFolderDialog.FolderName;
GlobalContext.MultipleLangSupport = Path.Exists(Path.Combine(folderPath, "quests", "lang"));
if (GlobalContext.MultipleLangSupport)
{
var enLangFile = Path.Combine(folderPath, "quests", "lang", "en_us.snbt");
var langData = StringNbt.Parse(File.ReadAllText(enLangFile));
_langs = [];
foreach (var item in langData)
{
var langKeys = item.Name.Split('.');
var langItem = new QuestLangModel
{
Type = langKeys[0],
Id = langKeys[1],
DocType = langKeys[2],
};
if (item is StringTag langTag)
{
langItem.Value = langTag.Value;
}
else if(item is ListTag valueList)
{
var descList = new StringBuilder();
foreach (var value in valueList)
{
descList.AppendLine((value as StringTag).Value);
}
langItem.Value = descList.ToString();
}
_langs.Add(langItem);
}
}
// 解析SNBT文件
//var data = SNBTParser.ParseFile(CurrentFilePath);
//NbtFile.Write(CurrentFilePath + ".r", questData, FormatOptions.None);
//StringNbt.SaveToFile(questData, CurrentFilePath + ".r",prettyPrint:true);
SnbtWriter.SaveToFile(questData, CurrentFilePath + ".r", prettyPrint: true);
if(_langs != null)
{
var windowTitle = "FTB Quests Editor - " + _langs.FirstOrDefault(it => it.Type == "file" && it.Id == "0000000000000001" && it.DocType == "title").Value;
Application.Current.MainWindow.Title = windowTitle;
}
if (Path.Exists(Path.Combine(folderPath, "quests", "chapters")))
{
var fullQuestsPath = Path.Combine(folderPath, "quests", "chapters");
var questFiles = Directory.GetFiles(fullQuestsPath, "*.snbt", SearchOption.AllDirectories);
_oriQuestsData = new List<CompoundTag>();
_updatedChapters = new List<string>();
Chapters = new ObservableCollection<ChapterModel>();
foreach (var file in questFiles)
{
var questData = StringNbt.Parse(File.ReadAllText(file));
_oriQuestsData.Add(questData);
var chapterId = (questData["id"] as StringTag)?.Value;
var chapterName = (questData["filename"] as StringTag)?.Value ;
var chapterTitle = questData.ContainsKey("title") ? (questData["title"] as StringTag)?.Value : "";
var chapter = new ChapterModel
{
Id = chapterId,
Name = chapterName,
Title = chapterTitle
};
if (GlobalContext.MultipleLangSupport)
{
chapter.Title = GetMutliLang("chapter",chapter.Id, "title");
}
Chapters.Add(chapter);
var quests = questData["quests"];
if(quests is ListTag questList)
{
foreach (var item in questList)
{
if (item is CompoundTag questTag)
{
var questId = (questTag["id"] as StringTag)?.Value;
var questTitle = "";
if (questTag.ContainsKey("title"))
{
questTitle = (questTag["title"] as StringTag)?.Value ?? "";
}
else if (GlobalContext.MultipleLangSupport)
{
questTitle = GetMutliLang("quest", questId, "title");
}
var questSubTitle = "";
if (questTag.ContainsKey("subtitle"))
{
questSubTitle = (questTag["subtitle"] as StringTag)?.Value ?? "";
}
else if (GlobalContext.MultipleLangSupport)
{
questSubTitle = GetMutliLang("quest", questId, "quest_subtitle");
}
var questDescriptions = new StringBuilder();
if (questTag.ContainsKey("description"))
{
if (questTag["description"] is ListTag descriptions)
{
foreach (var desc in descriptions)
{
questDescriptions.AppendLine((desc as StringTag)?.Value);
}
}
}
else if (GlobalContext.MultipleLangSupport)
{
var desc = GetMutliLang("quest", questId, "quest_desc");
questDescriptions.AppendLine(desc);
}
var questModel = new QuestModel
{
Id = questId,
Title = questTitle,
SubTitle = questSubTitle,
Description = questDescriptions.ToString()
};
chapter.Quests.Add(questModel);
}
}
}
}
SelectedQuests = Chapters.First()?.Quests ?? [];
}
else
{
MessageBox.Show("文件夹选择错误!");
}
//var questData = StringNbt.Parse(File.ReadAllText(CurrentFilePath));
//SnbtWriter.SaveToFile(questData, CurrentFilePath + ".r", prettyPrint: true);
}
catch (Exception ex)
{
@ -77,7 +190,50 @@ namespace MCI18n
[RelayCommand]
private void SaveQuestAs()
{
if (GlobalContext.MultipleLangSupport)
{
/// 另存为多语言文件
var saveFileDialog = new SaveFileDialog
{
Title = "保存多语言文件",
Filter = "SNBT 文件 (*.snbt)|*.snbt",
FileName = "zh_cn.snbt",
};
if (saveFileDialog.ShowDialog() == true)
{
try
{
var filePath = saveFileDialog.FileName;
var langData = new CompoundTag("");
foreach (var chapter in Chapters)
{
langData.Add(new StringTag($"chapter.{chapter.Id}.title", chapter.Title));
if (chapter.Quests != null)
{
foreach (var quest in chapter.Quests)
{
langData.Add(new StringTag($"quest.{quest.Id}.title", quest.Title));
langData.Add(new StringTag($"quest.{quest.Id}.quest_subtitle", quest.SubTitle));
var descLines = quest.Description.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
var descList = new ListTag($"quest.{quest.Id}.quest_desc",TagType.String);
foreach (var line in descLines)
{
descList.Add(new StringTag(null, line));
}
langData.Add(descList);
}
}
}
SnbtWriter.SaveToFile(langData, filePath, prettyPrint: true);
MessageBox.Show("多语言文件保存成功!", "成功", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
MessageBox.Show($"保存文件失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
}
[RelayCommand]
@ -85,5 +241,22 @@ namespace MCI18n
{
}
internal void ChapterSelectionChanged(ChapterModel selectedChapter)
{
if (selectedChapter != null && selectedChapter.Quests != null)
{
SelectedQuests = selectedChapter.Quests;
}
else
{
SelectedQuests = [];
}
}
private string GetMutliLang(string type,string id,string docType)
{
return _langs?.FirstOrDefault(it => it.Type == type && it.Id == id && it.DocType == docType)?.Value ?? "";
}
}
}

22
Models/ChapterModel.cs Normal file
View File

@ -0,0 +1,22 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MCI18n.Models
{
public partial class ChapterModel : ObservableObject
{
[ObservableProperty]
private string _id;
[ObservableProperty]
private string _name;
[ObservableProperty]
private string _title;
[ObservableProperty]
private ObservableCollection<QuestModel> _quests = new ObservableCollection<QuestModel>();
}
}

21
Models/QuestLangModel.cs Normal file
View File

@ -0,0 +1,21 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MCI18n.Models
{
public partial class QuestLangModel : ObservableObject
{
[ObservableProperty]
private string _type;
[ObservableProperty]
private string _id;
[ObservableProperty]
private string _docType;
[ObservableProperty]
private string _value;
}
}

30
Models/QuestModel.cs Normal file
View File

@ -0,0 +1,30 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MCI18n.Models
{
public partial class QuestModel : ObservableObject
{
[ObservableProperty]
private string _id;
[ObservableProperty]
private string _title;
[ObservableProperty]
private string _subTitle;
[ObservableProperty]
private string _description;
[ObservableProperty]
private ObservableCollection<TaskModel> _tasks;
}
}

17
Models/TaskModel.cs Normal file
View File

@ -0,0 +1,17 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MCI18n.Models
{
public partial class TaskModel : ObservableObject
{
[ObservableProperty]
private string _id;
[ObservableProperty]
private string _title;
}
}

View File

@ -1,68 +1,67 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style TargetType="Button" x:Key="MinecraftButtonStyle">
<Setter Property="Foreground" Value="White"/>
<Setter Property="Background" Value="#48494a"/>
<Setter Property="BorderBrush" Value="#373737"/>
<Setter Property="BorderThickness" Value="2"/>
<Setter Property="FontFamily" Value="Segoe UI"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Padding" Value="10,5"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Margin" Value="2"/>
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style TargetType="Button">
<Setter Property="Foreground" Value="White" />
<Setter Property="Background" Value="#48494a" />
<Setter Property="BorderBrush" Value="#373737" />
<Setter Property="BorderThickness" Value="2" />
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Padding" Value="10,5" />
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Margin" Value="2" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border
BorderBrush="Black"
BorderThickness="2"
>
<Border BorderBrush="Black" BorderThickness="2">
<Grid>
<!-- 按钮底部阴影 -->
<Border x:Name="ShadowBorder"
Background="#313233"
CornerRadius="0" />
<!-- 按钮底部阴影 -->
<Border
x:Name="ShadowBorder"
Background="#313233"
CornerRadius="0" />
<!-- 按钮主体 -->
<Border x:Name="MainBorder"
Background="#5B5B5B"
BorderBrush="#373737"
BorderThickness="2,2,2,4"
CornerRadius="0"
Margin="0,0,0,0">
<!-- 按钮主体 -->
<Border
x:Name="MainBorder"
Margin="0,0,0,0"
Background="#5B5B5B"
BorderBrush="#373737"
BorderThickness="2,2,2,4"
CornerRadius="0">
<ContentPresenter x:Name="ContentPresenter"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
TextElement.Foreground="{TemplateBinding Foreground}"/>
<ContentPresenter
x:Name="ContentPresenter"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
TextElement.Foreground="{TemplateBinding Foreground}" />
</Border>
</Grid>
</Border>
<ControlTemplate.Triggers>
<!-- 鼠标悬停效果 -->
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="MainBorder" Property="Background" Value="#6B6B6B"/>
<Setter TargetName="MainBorder" Property="BorderBrush" Value="#cccccc"/>
</Trigger>
<!-- 鼠标悬停效果 -->
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="MainBorder" Property="Background" Value="#6B6B6B" />
<Setter TargetName="MainBorder" Property="BorderBrush" Value="#cccccc" />
</Trigger>
<!-- 点击效果 -->
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="MainBorder" Property="Margin" Value="0,2,0,0"/>
<Setter TargetName="MainBorder" Property="BorderThickness" Value="2,2,2,2"/>
<Setter TargetName="ShadowBorder" Property="Visibility" Value="Hidden"/>
<!-- 点击效果 -->
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="MainBorder" Property="Margin" Value="0,2,0,0" />
<Setter TargetName="MainBorder" Property="BorderThickness" Value="2,2,2,2" />
<Setter TargetName="ShadowBorder" Property="Visibility" Value="Hidden" />
</Trigger>
</Trigger>
<!-- 禁用效果 -->
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="MainBorder" Property="Background" Value="#3F3F3F" />
<Setter TargetName="MainBorder" Property="BorderBrush" Value="#2A2A2A" />
<Setter Property="Foreground" Value="#909090" />
</Trigger>
</ControlTemplate.Triggers>
<!-- 禁用效果 -->
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="MainBorder" Property="Background" Value="#3F3F3F"/>
<Setter TargetName="MainBorder" Property="BorderBrush" Value="#2A2A2A"/>
<Setter Property="Foreground" Value="#909090"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>

View File

@ -0,0 +1,6 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style TargetType="ListView">
<Setter Property="Background" Value="Transparent" />
</Style>
</ResourceDictionary>

View File

@ -1,9 +1,9 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style TargetType="TabControl">
<Setter Property="Background" Value="#48494a" />
<Setter Property="Background" Value="#1f1f1f" />
</Style>
<Style TargetType="TabItem">
<Setter Property="Background" Value="#48494a" />
<Setter Property="Background" Value="#1f1f1f" />
<Setter Property="Foreground" Value="#fff" />
<Setter Property="Template">
<Setter.Value>

View File

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MCI18n.Utilities
{
public class GlobalContext
{
/// <summary>
/// FTB 高版本支持多语言文件 lang/en_us.snbt
/// </summary>
public static bool MultipleLangSupport = false;
}
}

File diff suppressed because it is too large Load Diff