C# 解释器 3

22 阅读4分钟

本文记录了笔者在阅读 WPF 程序过程中不懂的知识点和一些心得、理解,同时也是一种高效的学习方式的探索。

56. 使用 notepad 读取文本文件

private void ShowRunningLog()
{
    var fileName = "ProductionLine.txt";
    if (File.Exists(fileName))
    {
        Process.Start("notepad.exe", fileName);
    }
    else
    {
        MessageBox.Show("日志文件不存在");
    }
}

private void ShowReaderConfig()
{
    var fileName = "ReaderOptions.json";
    if (File.Exists(fileName))
    {
        Process.Start("notepad.exe", fileName).WaitForExit();
    }
    else
    {
        MessageBox.Show("扫码枪配置文件不存在");
    }
}

57. 列表信息超过 100 条的时候移除前 50 条

if (Messages.Count > 100)
{
    var count = 0;
    while (count++ < 50)
    {
        Messages.RemoveAt(0);
    }
}

58. 多线程共用属性的锁线程修改

private AutoResetEvent signal = new AutoResetEvent(true);

[BindingProperty(Target = "newMessage")]
public string NewMessage
{
    get => newMessage;
    set
    {
        signal.WaitOne();
        newMessage = value;
        AsyncUIAction(() =>
        {
            Messages.Add($"{DateTime.Now:G} {newMessage}");
            RaisedPropertyChanged(nameof(Messages));
        });
        signal.Set();
    }
}

...

public virtual void AsyncUIAction(Action action)
{
    System.Windows.Application.Current?.Dispatcher.Invoke(action);
}

59. 字符串空值检测 string.IsNullOrEmpty(WriteValue)

if (string.IsNullOrEmpty(WriteValue))
{
    return;
}

60. 相同赋值不触发

string frontShell;
public string FrontShell
{
    get => frontShell;
    set
    {
        if (value == frontShell)
        {
            return;
        }
        frontShell = value;
        RaisedPropertyChanged();
    }
}

61. 将每种标签的样式单独创建一个文件保存:Styles/Button.xaml

62. 引入外部程序集并声明需要使用的函数签名

[DllImport("VisionAlgorithmCheckTarget.dll", CharSet = CharSet.Ansi)]
static extern int TargetCheck(IntPtr imgData,int w,int h, IntPtr jsonParam, IntPtr jsonOut);

63. 文件对话框

private void Button_Click(object sender, RoutedEventArgs e)
{
    using (OpenFileDialog openFileDialog = new OpenFileDialog())
    {
        openFileDialog.InitialDirectory = "D:\\"; // 设置初始目录
        openFileDialog.Filter = "图片文件(*.jpg;*.jpeg;*.gif;*.bmp;*.png)|*.jpg;*.jpeg;*.gif;*.bmp;*.png"; // 设置文件过滤器
        openFileDialog.FilterIndex = 2;
        openFileDialog.RestoreDirectory = true;

        if (openFileDialog.ShowDialog() == DialogResult.OK)
        {
            // 获取选定的文件路径
            Console.WriteLine(openFileDialog.FileName);
        }
    }
}

64. 获取某个范围之内的随机数

var rand = new Random();
rand.Next(allImgs.Count)

上述代码用来随机获取一张图片集中的图片。

65. 只读的 checkbox 并在输入正确密码之后自动选中

先构建 UI 界面,包括一个标签、一个密码输入框和一个登录按钮。

<StackPanel Grid.Row="1" Grid.Column="1" HorizontalAlignment="Center" >
    <CheckBox Content="工程师" HorizontalAlignment="Center" Foreground="WhiteSmoke" VerticalAlignment="Center" HorizontalContentAlignment="Left" VerticalContentAlignment="Center"  FontSize="20" IsEnabled="False">
        <CheckBox.Style>
            <Style TargetType="CheckBox">
                <Setter Property="Foreground" Value="DarkBlue"/>
                <Setter Property="LayoutTransform">
                    <Setter.Value>
                        <ScaleTransform ScaleX="1.5" ScaleY="1.5" />
                    </Setter.Value>
                </Setter>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding IsEngineer,UpdateSourceTrigger=PropertyChanged}" Value="false">
                        <Setter Property="IsChecked"  Value="False"></Setter>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding IsEngineer,UpdateSourceTrigger=PropertyChanged}" Value="true">
                        <Setter Property="IsChecked"  Value="True"></Setter>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </CheckBox.Style>
    </CheckBox>
    <StackPanel Orientation="Vertical"  Margin="0,5" >
        <Label  Content="密码" FontSize="30" FontWeight="Bold" BorderThickness="0" Background="Transparent" Foreground="WhiteSmoke"/>
        <PasswordBox x:Name="PWD1" HorizontalContentAlignment="Left" VerticalContentAlignment="Center" FontSize="15" Width="200" Height="30" Margin="2"/>
    </StackPanel>
    <Button  Margin="5 0" Height="40" Width="100" FontSize="20" Content="登录" Click="Button_Click"  Background="{DynamicResource ButtonBackgroundColor8}"/>
</StackPanel>

下面是上述 UI 代码的解释:

主体结构

<StackPanel Grid.Row="1" Grid.Column="1" HorizontalAlignment="Center">
  • 创建一个 StackPanel 布局容器。
  • Grid.Row="1" Grid.Column="1":指定这个 StackPanel 位于父容器中的 Grid 布局的第 1 行第 1 列。
  • HorizontalAlignment="Center":将 StackPanel 水平居中对齐。

第一部分:工程师复选框

<CheckBox Content="工程师" HorizontalAlignment="Center" Foreground="WhiteSmoke" VerticalAlignment="Center" HorizontalContentAlignment="Left" VerticalContentAlignment="Center" FontSize="20" IsEnabled="False">
  • 创建一个 CheckBox 控件。
  • Content="工程师":显示的文本内容为“工程师”。
  • HorizontalAlignment="Center":将复选框水平居中对齐。
  • Foreground="WhiteSmoke":字体颜色设置为浅灰色(WhiteSmoke)。
  • VerticalAlignment="Center":将复选框垂直居中对齐。
  • HorizontalContentAlignment="Left":复选框内容(文本)水平左对齐。
  • VerticalContentAlignment="Center":复选框内容垂直居中对齐。
  • FontSize="20":字体大小为 20。
  • IsEnabled="False":复选框默认禁用(不可点击)。
<CheckBox.Style>
    <Style TargetType="CheckBox">
  • 定义一个针对 CheckBox 的样式。
<Setter Property="Foreground" Value="DarkBlue"/>
  • 设置 CheckBox 的字体颜色为深蓝色(DarkBlue),注意这里的属性值会覆盖前面设定的 Foreground="WhiteSmoke"
<Setter Property="LayoutTransform">
    <Setter.Value>
        <ScaleTransform ScaleX="1.5" ScaleY="1.5" />
    </Setter.Value>
</Setter>
  • 使用 LayoutTransformCheckBox 进行二维变换。
  • ScaleTransform ScaleX="1.5" ScaleY="1.5":将 CheckBox 控件放大 1.5 倍。
<Style.Triggers>
    <DataTrigger Binding="{Binding IsEngineer,UpdateSourceTrigger=PropertyChanged}" Value="false">
        <Setter Property="IsChecked" Value="False"></Setter>
    </DataTrigger>
    <DataTrigger Binding="{Binding IsEngineer,UpdateSourceTrigger=PropertyChanged}" Value="true">
        <Setter Property="IsChecked" Value="True"></Setter>
    </DataTrigger>
</Style.Triggers>
  • 定义数据触发器,根据 IsEngineer 属性的值来设置 CheckBoxIsChecked 属性。
  • Binding="{Binding IsEngineer,UpdateSourceTrigger=PropertyChanged}":绑定到 IsEngineer 属性,UpdateSourceTrigger=PropertyChanged 表示在属性值变化时立即更新。
  • Value="false"Value="true":分别表示 IsEngineer 属性为假和真时的触发逻辑。
  • Setter Property="IsChecked":设置 CheckBox 的勾选状态。

第二部分:密码输入框

<StackPanel Orientation="Vertical" Margin="0,5">
  • 创建一个竖向排列的 StackPanel
  • Orientation="Vertical":子元素垂直排列。
  • Margin="0,5":设置外边距为 0(上下)和 5(左右)。
<Label Content="密码" FontSize="30" FontWeight="Bold" BorderThickness="0" Background="Transparent" Foreground="WhiteSmoke"/>
  • 创建一个 Label 控件。
  • Content="密码":显示的文本内容为“密码”。
  • FontSize="30":字体大小为 30。
  • FontWeight="Bold":字体加粗。
  • BorderThickness="0":边框厚度为 0。
  • Background="Transparent":背景颜色为透明。
  • Foreground="WhiteSmoke":字体颜色为浅灰色。
<PasswordBox x:Name="PWD1" HorizontalContentAlignment="Left" VerticalContentAlignment="Center" FontSize="15" Width="200" Height="30" Margin="2"/>
  • 创建一个 PasswordBox 控件(密码输入框)。
  • x:Name="PWD1":设置控件名称为 PWD1
  • HorizontalContentAlignment="Left":输入内容水平左对齐。
  • VerticalContentAlignment="Center":输入内容垂直居中对齐。
  • FontSize="15":字体大小为 15。
  • Width="200"Height="30":设置宽度为 200,高度为 30。
  • Margin="2":设置外边距为 2。

第三部分:登录按钮

<Button Margin="5 0" Height="40" Width="100" FontSize="20" Content="登录" Click="Button_Click" Background="{DynamicResource ButtonBackgroundColor8}"/>
  • 创建一个 Button 控件。
  • Margin="5 0":设置外边距为 5(上下)和 0(左右)。
  • Height="40"Width="100":设置按钮的高度为 40,宽度为 100。
  • FontSize="20":字体大小为 20。
  • Content="登录":按钮上的文本为“登录”。
  • Click="Button_Click":点击按钮时调用的事件处理方法为 Button_Click
  • Background="{DynamicResource ButtonBackgroundColor8}":按钮的背景颜色绑定到动态资源 ButtonBackgroundColor8

总结

这段代码定义了一个包含以下元素的用户界面布局:

  1. 一个禁用的“工程师”复选框。
  2. 一个带“密码”标签和密码输入框的容器。
  3. 一个带有点击事件的登录按钮。

然后是点击登录按钮之后的回调:

private void Button_Click(object sender, RoutedEventArgs e)
{
    if (PWD1.Password == "***")
    {
        var loginModel = ApplicationInfo.ServiceProvider.GetViewModel<Home_VisionProjectViewModel>();
        loginModel.IsEngineer = true;
        MessageBoxX.Show("工程师身份登录成功!");
        PWD1.Password = "";
    }
    else
        MessageBoxX.Show("密码输入错误,请重试!");
}

66. 定义扩展名数组

string[] imageExtensions = { ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".ico" };
string[] txtExtensions = { ".txt" };

67. 手动拼接路径

string ClassFile = LabelPath + "\\classes.txt";

68. System.IO 中的一些常用的方法

以下是对于你提到的五个方法的逐条解释:

1. System.IO.Path.GetFullPath(string path)

  • 功能:获取指定路径的绝对路径。

  • 参数

    • path:表示路径的字符串,可以是相对路径或绝对路径。
  • 返回值

    • 返回一个字符串,表示指定路径的完整绝对路径。路径会被规范化,例如,多余的分隔符会被移除,相对路径(如 ...)会被解析为绝对路径。
  • 用途

    • 在需要获取文件或目录的绝对路径时使用,确保路径的合法性。

    • 示例:

      string relativePath = "../folder/file.txt";
      string fullPath = Path.GetFullPath(relativePath);
      Console.WriteLine(fullPath); // 输出当前工作目录的完整路径,例如:"C:\Users\username\folder\file.txt"
      

2. System.IO.Directory.Exists(string path)

  • 功能:检查指定的目录是否存在。

  • 参数

    • path:表示目录路径的字符串。
  • 返回值

    • 返回一个布尔值,如果目录存在返回 true,否则返回 false
  • 用途

    • 在执行文件或目录操作之前,先检查目录是否存在,以避免不必要的错误。

    • 示例:

      string directoryPath = @"C:\Users\username\Desktop\MyFolder";
      bool exists = Directory.Exists(directoryPath);
      Console.WriteLine(exists); // 如果目录存在,输出 "True",否则 "False"
      

3. System.IO.Directory.GetFiles(string path)

  • 功能:获取指定目录中的文件列表。

  • 参数

    • path:表示目录路径的字符串。
  • 返回值

    • 返回一个字符串数组,包含指定目录中的所有文件的绝对路径。如果目录为空或不存在,则返回空数组。
  • 用途

    • 需要获取目录中的文件列表时使用,例如批量处理文件。

    • 示例:

      string directoryPath = @"C:\Users\username\Desktop\Documents";
      string[] files = Directory.GetFiles(directoryPath);
      foreach (string file in files)
      {
          Console.WriteLine(file); // 输出每个文件的路径
      }
      

4. System.IO.Path.GetExtension(string path)

  • 功能:获取文件的扩展名。

  • 参数

    • path:表示文件路径的字符串。
  • 返回值

    • 返回一个字符串,表示文件的扩展名(包含前面的点,例如 .txt.jpg)。如果文件没有扩展名或路径不合法,则返回空字符串。
  • 用途

    • 在需要判断文件类型或根据扩展名进行操作时使用。

    • 示例:

      string filePath = "document.txt";
      string extension = Path.GetExtension(filePath);
      Console.WriteLine(extension); // 输出 ".txt"
      

5. System.IO.File.ReadAllText(string path)

  • 功能:读取文件的全部内容。

  • 参数

    • path:表示文件路径的字符串。
  • 返回值

    • 返回一个字符串,包含文件的全部内容。
  • 用途

    • 当需要快速读取整个文本文件的内容时使用,支持文本编码自动检测。

    • 示例:

      string filePath = "info.txt";
      string content = File.ReadAllText(filePath);
      Console.WriteLine(content); // 输出文件的全部内容
      

这些方法是 .NET 中处理文件和目录操作时非常常用的工具,能够帮助开发者更高效地进行文件相关的开发工作。

69. 做一个表格页

<Grid Grid.Row="0">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="0.7*"/>
        <ColumnDefinition Width="1*"/>
        <ColumnDefinition Width="0.7*"/>
        <ColumnDefinition Width="1*"/>
        <ColumnDefinition Width="0.7*"/>
        <ColumnDefinition Width="1*"/>
        <ColumnDefinition Width="0.7*"/>
        <ColumnDefinition Width="1*"/>
        <ColumnDefinition Width="1.1*"/>
        <ColumnDefinition Width="1.1*"/>
    </Grid.ColumnDefinitions>
    <TextBlock Grid.Column="0"  Text="开始日期" VerticalAlignment="Center" FontSize="25" Foreground="White"/>
    <hc:DatePicker Grid.Column="1" x:Name="startTime" ShowClearButton="False" Width="120" Height="40"  Margin="10,0,30,0"/>
    <TextBlock Grid.Column="2"  Text="结束日期" VerticalAlignment="Center" FontSize="25" Foreground="White"/>
    <hc:DatePicker Grid.Column="3" x:Name="endTime" ShowClearButton="False" Width="120" Height="40" Margin="10,0,30,0"/>
    <TextBlock Text="序列号" Grid.Column="4" VerticalAlignment="Center" FontSize="25" Foreground="White"/>
    <TextBox Text="{Binding SNKey}"  Grid.Column="5" Width="120" Height="40" Margin="10,0,30,0"/>
    <TextBlock Text="订单号" Grid.Column="6"  VerticalAlignment="Center" FontSize="25" Foreground="White"/>
    <TextBox Text="{Binding OrderKey}" Grid.Column="7"  Width="120" Height="40" Margin="10,0,30,0"/>
    <Button Grid.Column="8" Content="查询" Foreground="White" Margin="10 0 10 0"  FontSize="20"  Width="150" Height="85" Click="Button_Click_1" Background="{DynamicResource ButtonBackgroundColor}" />
    <Button Grid.Column="9" Content="导出" Foreground="White" Margin="10 0 10 0"  FontSize="20"  Width="150" Height="85" Click="Button_Click" Background="{DynamicResource ButtonBackgroundColor}" />
</Grid>
Page>Grid>(Grid.Resources>ResourceDictionary>ResourceDictionary.MergedDictionaries>ResourceDictionary*2)+(TabControl>TabItem>Grid>(Grid.RowDefinations>RowDefination*4)+(Grid>Grid.ColumnDefinations>ColumnDefination*10))

表格的内容页

Grid>DataGrid>DataGrid.Columns[ItemSource={}]>DataGridTextColumn[Binding={Binding .} Header="***" Width=***]*8

70. 文本转时间格式

DateTime T1 = Convert.ToDateTime(startTime.Text);
DateTime T2 = Convert.ToDateTime(endTime.Text).AddDays(1); //默认结束加1天

71. 使用 ISqlSugarClient 查询数据并输出到指定变量中去

使用的 API 名称主要有 where 和 ToList.

// 获取注入的服务单例
var ctx = ApplicationInfoBase.ServiceProvider.GetService<ISqlSugarClient>()
// 筛选数据
var resultitem = ctx.Queryable<TestingRecord>().Where(g => g.TestTime <= T2 && g.TestTime >= T1 && g.SN == home_VisionRecordPageViewModel.SNKey && g.OrderCode == home_VisionRecordPageViewModel.OrderKey).ToList();
// 将筛选得到的数据输出
RecordData.ItemsSource = resultitem;

72. 将 DataGrid 中的数据输出成 Excel 文件

以下是将 DataGrid 中的数据输出成 Excel 文件的代码拆解及说明:

代码拆解

1. 检查数据是否为空

csharp复制

if (RecordData.Items.Count == 0)
{
    MessageBoxX.Show("未查询到要导出的数据,请重试!");
    return;
}
  • 作用:检查 DataGrid 中是否有数据。

  • 解释

    • RecordData.Items.Count 返回 DataGrid 中的项数。
    • 如果没有数据,弹出提示框,提示用户“未查询到要导出的数据”,并终止后续操作。

2. 选择所有单元格

csharp复制

RecordData.SelectAllCells();
  • 作用:选中 DataGrid 中的所有单元格。

  • 解释

    • SelectAllCells()DataGrid 的方法,用于选中所有可见的单元格。

3. 设置剪贴板复制模式

csharp复制

RecordData.ClipboardCopyMode = System.Windows.Controls.DataGridClipboardCopyMode.IncludeHeader;
  • 作用:设置 DataGrid 的剪贴板复制模式为包含列头。

  • 解释

    • ClipboardCopyModeDataGrid 的一个属性,指定在复制到剪贴板时是否包含列头。
    • IncludeHeader 表示复制内容时包含列头。

4. 执行复制操作

csharp复制

ApplicationCommands.Copy.Execute(null, RecordData);
  • 作用:将 DataGrid 中选中的内容复制到剪贴板中。

  • 解释

    • ApplicationCommands.Copy 是执行复制操作的命令。

5. 获取剪贴板中的数据

csharp复制

string result = (string)Clipboard.GetData(DataFormats.Text);
  • 作用:从剪贴板中获取复制的数据。

  • 解释

    • Clipboard.GetData(DataFormats.Text) 获取剪贴板中的文本格式数据。

6. 打开文件保存对话框

csharp复制

SaveFileDialog sfd = new SaveFileDialog();
sfd.FileName = "测试记录";
sfd.Filter = "Excel文件(*.xls)|*.xls|Csc文件(*.csv)|*.csv|所有文件(*.*)|*.*";
  • 作用:允许用户选择保存位置和文件类型。

  • 解释

    • SaveFileDialog 是一个文件保存对话框,用于用户选择保存位置。
    • FileName 设置默认的文件名。
    • Filter 设置文件类型过滤器,支持 Excel 文件、Csc 文件和所有文件。

7. 处理文件保存

csharp复制

if (sfd.ShowDialog() == DialogResult.OK)
{
    string path = System.IO.Path.GetDirectoryName(sfd.FileName);
    RecordData.UnselectAllCells();
    StreamWriter swr = new StreamWriter(sfd.FileName, false, Encoding.GetEncoding("gb2312"));
    swr.WriteLine(result.Replace(',', ' '));
    swr.Flush();
    swr.Close();
    swr.Dispose();
    MessageBoxX.Show($"数据导出成功!");
}
  • 作用:将数据写入到用户选择的文件中。

  • 解释

    • ShowDialog() 显示文件保存对话框,等待用户选择。

    • 如果用户点击了“确定”,继续执行以下操作:

      • UnselectAllCells() 取消选中 DataGrid 中的所有单元格。
      • StreamWriter 是用于写入文件的类。
      • Encoding.GetEncoding("gb2312") 指定文件使用的编码。
      • swr.WriteLine(result.Replace(',', ' ')) 将剪贴板中的数据写入文件。
      • Flush()Close() 确保数据成功写入并关闭文件。
      • 弹出提示框,提示用户“数据导出成功”。

8. 异常处理

csharp复制

catch (Exception ex)
{
    MessageBoxX.Show($"数据导出失败,具体原因{ex}");
}
  • 作用:捕获并处理导出过程中可能发生的异常。

  • 解释

    • 如果导出过程中发生异常,会捕获异常并弹出提示框,显示异常信息。

工作原理总结

  1. 首先检查 DataGrid 中是否有数据。
  2. 选中 DataGrid 中的所有单元格,并设置复制模式为包含列头。
  3. 将选中的内容复制到剪贴板。
  4. 弹出文件保存对话框,用户选择保存位置和文件类型。
  5. 将剪贴板中的数据写入到指定文件中。
  6. 使用 StreamWriter 将数据写入到文件中。
  7. 如果文件写入成功,弹出提示框,提示用户数据导出成功;如果失败,捕获异常并提示用户。

注意事项

  1. DataGrid 的列头必须可见,因为 ClipboardCopyMode 设置为 IncludeHeader,否则列头不会被复制。
  2. 如果导出的数据包含特殊字符(如逗号、换行符等),可能需要进一步处理。
  3. 在实际应用中,可以考虑将编码设置为 Encoding.UTF8 或其他编码,以支持更多字符集。
  4. 如果需要更多的格式化(如合并单元格、设置样式等),可以使用第三方库(如 EPPlus 或 ClosedXML)来生成 Excel 文件。

全部代码

private void Button_Click(object sender, RoutedEventArgs e)
{
    try
    {
        if (RecordData.Items.Count == 0)
        {
            MessageBoxX.Show("未查询要导出的数据,请重试!");
            return;
        }
        RecordData.SelectAllCells();

        RecordData.ClipboardCopyMode = System.Windows.Controls.DataGridClipboardCopyMode.IncludeHeader;

        ApplicationCommands.Copy.Execute(null, RecordData);

        string result = (string)Clipboard.GetData(DataFormats.Text);

        SaveFileDialog sfd = new SaveFileDialog();

        sfd.FileName = "测试记录";
        sfd.Filter = "Excel文件(*.xls)|*.xls|Csc文件(*.csv)|*.csv|所有文件(*.*)|*.*"; ;

        if (sfd.ShowDialog() == DialogResult.OK)
        {
            string path = System.IO.Path.GetDirectoryName(sfd.FileName);
            RecordData.UnselectAllCells();
            StreamWriter swr = new StreamWriter(sfd.FileName, false, Encoding.GetEncoding("gb2312"));
            //StreamWriter swr = new StreamWriter(sfd.FileName, false, System.Text.Encoding.GetEncoding(-0));
            swr.WriteLine(result.Replace(',', ' '));
            swr.Flush();
            swr.Close();
            swr.Dispose();
                    
            MessageBoxX.Show($"数据导出成功!");
        }

    }
    catch (Exception ex)
    {
        MessageBoxX.Show($"数据导出失败,具体原因{ex}");
    }

}

73. 构建一个带图标的按钮

全部代码如下

<RadioButton IsChecked="True" 
                Content="关于" 
                Style="{DynamicResource MenuRadioButtonStyle2}" 
                Tag="{DynamicResource report}" Margin="0 5 0 5"
                Command="{Binding ShowPageCommand}"
                CommandParameter="/Views/Pages/Help/Help_AboutPage.xaml"/>
---

<Style x:Key="MenuRadioButtonStyle2" TargetType="{x:Type RadioButton}">
    <Setter Property="Background" Value="Transparent"/>
    <Setter Property="Foreground" Value="{DynamicResource PrimaryForegroundColor}"/>
    <Setter Property="Cursor" Value="Hand"/>
    <Setter Property="FontWeight" Value="Normal"/>
    <Setter Property="FontSize" Value="36"/>
    <Setter Property="Height" Value="60"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type RadioButton}">
                <Border x:Name="menuButton"  Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="60"/>
                            <ColumnDefinition/>
                        </Grid.ColumnDefinitions>

                        <!-- Selected  -->
                        <Border x:Name="btnSelected" Grid.ColumnSpan="2"  CornerRadius="4" />

                        <!-- Indicator -->
                        <!--
                        <Rectangle Name="Indicator"  Grid.ColumnSpan="2" HorizontalAlignment="Left" Width="4" Height="40" VerticalAlignment="Center" RadiusX="2" RadiusY="2"/>-->
                        <!-- Icon -->
                        <Path x:Name="Icon" Data="{Binding Tag, RelativeSource={RelativeSource AncestorType={x:Type RadioButton}}}" Height="30" Width="30" Stretch="None" Fill="{DynamicResource PrimaryForegroundColor}" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="20 0 2 0"/>

                        <!-- Text -->
                        <TextBlock x:Name="txtName" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="10 0 10 0"
                                            Grid.Column="1" Text="{TemplateBinding Content}" Foreground="{TemplateBinding Foreground}" 
                                            FontWeight="{TemplateBinding FontWeight}"
                                            FontSize="{TemplateBinding FontSize}"/>
                    </Grid>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter TargetName="txtName" Property="Foreground" Value="{DynamicResource MenuForegroundColor}"/>
                        <Setter TargetName="Icon" Property="Fill" Value="{DynamicResource MenuForegroundColor}"/>
                        <!-- Optional 
                        <Setter TargetName="Indicator" Property="Fill" Value="red"/>
                        <Setter TargetName="Indicator" Property="Height" Value="20"/>-->
                    </Trigger>
                    <Trigger Property="IsChecked" Value="True">
                        <Setter TargetName="Icon" Property="Fill" Value="{DynamicResource MenuForegroundColor}"/>
                           
                        <Setter TargetName="btnSelected" Property="Background" Value="{DynamicResource BackgroundColor3}"/>
                        <Setter TargetName="txtName" Property="Foreground" Value="{DynamicResource MenuForegroundColor}"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

74. 点击按钮切换视图

首先使用 Frame 组件创建一个视图窗口

<Frame x:Name="PagesNavigation" NavigationUIVisibility="Hidden" Source="{Binding ChildPageUri}">

然后将改变 ChildPageUri 这个属性的回调函数绑定在按钮命令上即可:

<RadioButton IsChecked="True" 
                Content="按钮名称" 
                Style="{DynamicResource MenuRadioButtonStyle2}" 
                Tag="{DynamicResource report}" Margin="0 5 0 5"
                Command="{Binding ShowPageCommand}"
                CommandParameter="/Views/Pages/*/**.xaml"/>

ChildPageUri 属性的定义及按钮的回调函数

        private string childPageUri = "/Views/Pages/*/**.xaml";

        public string ChildPageUri
        {
            get { return childPageUri; }
            set { childPageUri = value; RaisedPropertyChanged(); }
        }
        public ICommand ShowPageCommand => new RelayCommand<string>(ShowPage);

        private void ShowPage(string uri)
        {
            ChildPageUri = uri;
        }

75. Frame 组件

<Frame Grid.Column="1" Source="{Binding ChildPageUri}" NavigationUIVisibility="Hidden"></Frame>

1. Frame 组件

  • Frame 是 WPF 中用于导航的容器控件,用于在应用程序中加载和显示页面(Page)。
  • 它类似于网页中的框架,可以将页面加载到 Frame 中,并在 Frame 中导航。

2. Grid.Column="1"

  • Grid.ColumnGrid 布局中的重要属性,用于指定控件所在的列。
  • 这里的 Grid.Column="1" 表示该 Frame 被放置在 Grid 的第 1 列(列索引从 0 开始)。
  • Grid 是一种常见的布局控件,通过定义行(RowDefinitions)和列(ColumnDefinitions)来组织 UI 元素。

3. Source="{Binding ChildPageUri}"

  • Source 属性指定 Frame 要加载的页面的 URI。
  • "{Binding ChildPageUri}" 是数据绑定语法,表示 Source 属性绑定到数据上下文中的 ChildPageUri 属性。
  • 这意味着 ChildPageUri 的值可以动态变化,当 ChildPageUri 的值改变时,Frame 会加载新的页面。

4. NavigationUIVisibility="Hidden"

  • NavigationUIVisibility 控制 Frame 是否显示导航 UI(如“后退”和“前进”按钮)。
  • 其值为 "Hidden" 表示隐藏导航 UI,这意味着用户无法通过 Frame 的内置控件进行导航,开发者需要手动实现导航逻辑。

综合解释

这段代码定义了一个 Frame 组件,其主要功能和特性如下:

  1. 布局位置:通过 Grid.Column="1" 指定 Frame 位于 Grid 的第 1 列。
  2. 页面加载:使用 Source="{Binding ChildPageUri}" 绑定到数据上下文中的 ChildPageUri 属性,动态加载指定的页面。
  3. 隐藏导航 UI:通过 NavigationUIVisibility="Hidden" 隐藏了 Frame 的默认导航按钮,需要通过其他方式实现页面导航逻辑。

示例场景

假设有一个主页面(MainPage),其中包含一个 Grid 和一个 Frame,开发者希望通过导航到不同的页面来实现功能切换。以下是一个简单的示例:

MainPage.xaml

<Page x:Class="MyApp.MainPage">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!-- 导航按钮 -->
        <Button Grid.Row="0" Content="Go to Page1" Click="GoToPage1_Click"/>
        <Button Grid.Row="0" Content="Go to Page2" Click="GoToPage2_Click"/>

        <!-- Frame 用于加载页面 -->
        <Frame Grid.Row="1" Source="{Binding CurrentPageUri}" NavigationUIVisibility="Hidden"/>
    </Grid>
</Page>

MainPage.xaml.cs

public partial class MainPage : Page, INotifyPropertyChanged
{
    private string _currentPageUri;

    public string CurrentPageUri
    {
        get { return _currentPageUri; }
        set
        {
            _currentPageUri = value;
            OnPropertyChanged();
        }
    }

    public MainPage()
    {
        InitializeComponent();
        DataContext = this;
    }

    private void GoToPage1_Click(object sender, RoutedEventArgs e)
    {
        CurrentPageUri = "Page1.xaml";
    }

    private void GoToPage2_Click(object sender, RoutedEventArgs e)
    {
        CurrentPageUri = "Page2.xaml";
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string name = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }
}

运行效果

  • 点击“Go to Page1”按钮时,CurrentPageUri 被设置为 Page1.xamlFrame 会加载 Page1.xaml 页面。
  • 点击“Go to Page2”按钮时,CurrentPageUri 被设置为 Page2.xamlFrame 会加载 Page2.xaml 页面。
  • Frame 的导航 UI 被隐藏,用户无法通过 Frame 的内置按钮进行导航,而是通过按钮点击事件实现自定义导航。