首页 > 解决方案 > 源代码更改时 WPF 自动构建和重新加载

问题描述

我看到了一些关于它的答案,建议使用 Live Unit Testing 功能。所以我没有 Microsoft Enterprise,因此无法使用它的 Live Unit Testing 功能。试图创建一个简单的应用程序以编译并重新加载到 WPF 容器窗口中。

骨架代码如下所示:

public void RecompileAndReloadPrj ()
{
   Grid.Content = null;
   ReleaseAsm()
   RunMsBuild(targetProject);
   Grid.Content = LoadComponetFromAsm(targetASM);
}

不幸的是,让它工作起来有点复杂……有没有人有现成的代码可以发布、提示等,或者提供链接?

标签: c#wpfvisual-studio

解决方案


是的,在几年前,我如您所愿为 WPF 编写了一些代码。但它是非常基本的,并且有很多问题。但我建议你,在调试时检查 Edit XAML 的特性。即使没有断点,它也能很好地工作。只需在调试模式下运行项目并编辑 XAML 文件,即使您的代码通过后面的代码加载它。甚至不需要保存。

我在这里列出了代码和一些关于它的评论:

主窗口.xaml

<Window x:Class="RebuildAndReloadWPF.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:RebuildAndReloadWPF"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800" Activated="Window_Activated" >
    <Grid >
        <Grid.RowDefinitions>
            <RowDefinition Height="40"/>


            <RowDefinition />
        </Grid.RowDefinitions>
        <Border CornerRadius="6" BorderBrush="Gray" Background="LightGray" BorderThickness="2" >
            <StackPanel x:Name="___No_Name_" Background="LightYellow" Orientation="Horizontal">
                <Button Click="Button_Click" >Reload</Button>
               <CheckBox x:Name="chkReloadOnFocus" VerticalContentAlignment="Center" VerticalAlignment="Center" Content="Reload on focus" Margin="10,0,0,0"/>
                <TextBlock x:Name="txtIndicator" VerticalAlignment="Center" Margin="10,0,0,0"/>
                <Border x:Name="elmJustNowIndicator" BorderThickness="1" BorderBrush="Black" Background="Orange" CornerRadius="5" Height="21" Width="76" Visibility="Hidden" Margin="10,0,0,0">
                    <TextBlock Text="Just now" HorizontalAlignment="Center" VerticalAlignment="Center"/>
                </Border>
            </StackPanel>
        </Border>
        <ScrollViewer x:Name="PlaceHolder" Grid.Row="1"/>

    </Grid>
</Window>

主窗口.xaml.cs

using Microsoft.Build.BuildEngine;
using System;
using System.IO;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Threading;

namespace RebuildAndReloadWPF
{
    /// <summary>
   /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        const string projectPath = @"C:\...\Some-Project.csproj";
        const string libFileName = "Some-Lib.dll";
        const string ClassName = "NameSpace.UserControlName";


        private string projectFileName;
        private string projectDirectoryPath;
        private string projectBinPath;
        private string logFilePath;
        private string appDirectoryPath;
        private DispatcherTimer indicatorTimer = new DispatcherTimer();


        public MainWindow ()
        {
            projectFileName = Path.GetFileName(projectPath);
            projectDirectoryPath = Path.GetDirectoryName(projectPath);
            projectBinPath = projectDirectoryPath + @"\bin\Debug";
            logFilePath = projectDirectoryPath + @"\bin\Debug\build.log";
            appDirectoryPath = AppDomain.CurrentDomain.BaseDirectory;
            indicatorTimer = new DispatcherTimer();
            indicatorTimer.Interval = TimeSpan.FromMilliseconds(4000);
            indicatorTimer.Tick += ( sender, e ) =>
            {
                elmJustNowIndicator.Visibility = Visibility.Hidden;
                indicatorTimer.IsEnabled = false;
            };

            InitializeComponent();
        }


        public void ReloadContainer ()
        {
            PlaceHolder.Content = null;

            bool result = RunMsBuild();

            if (!result)
            {
                txtIndicator.Text = "Compile error, see in log file. Compile fail at: " + DateTime.Now.ToLongTimeString();
                txtIndicator.Foreground = Brushes.Red;
                return;
            }
            else
            {
                txtIndicator.Text = "Last build at: " + DateTime.Now.ToLongTimeString();
                txtIndicator.Foreground = Brushes.Green;
            }

            try
            {
                File.Copy(projectBinPath + @"\" + libFileName, appDirectoryPath + libFileName, true);
            }
            catch (Exception ex)
            {
                MessageBox.Show("Can't copy compiled lib file: " + ex.Message);
                throw;
            }

            elmJustNowIndicator.Visibility = Visibility.Visible;
            indicatorTimer.IsEnabled = false;
            indicatorTimer.IsEnabled = true;

            try
            {
                PlaceHolder.Content = AsmLoad();
            }
            catch (Exception ex)
            {
                MessageBox.Show("Laod assembly error" + ex.Message);
            }

            GC.Collect();
        }


        public bool RunMsBuild ()
        {
            Engine eng = new Engine();

            FileLogger logger = new FileLogger();
            logger.Parameters = "logfile=" + logFilePath;
            eng.RegisterLogger(logger);

            bool bb = eng.BuildProjectFile(projectPath);
            eng.UnregisterAllLoggers();
            return bb;
        }

        public FrameworkElement AsmLoad ()
        {
            byte[] assemblyFileBUffer = File.ReadAllBytes(appDirectoryPath + @"\" + libFileName);
            Assembly asm = AppDomain.CurrentDomain.Load(assemblyFileBUffer);
            ContentControl container = (ContentControl)asm.CreateInstance(ClassName);
            return (FrameworkElement)container.Content;
        }


        private void Window_Activated ( object sender, EventArgs e )
        {
            if (chkReloadOnFocus.IsChecked == true)
                ReloadContainer();
        }

        private void Button_Click ( object sender, RoutedEventArgs e )
        {
            ReloadContainer();
        }
    }
}

注释:

  1. 上面的代码仅适用于 UserControl 类。您可以扩展代码以支持 Windows 和面板。

  2. 上面的代码仅在我们的应用程序窗口激活(焦点)时进行重建。您可以将其展开以响应文件保存。通过与 FileSystemWatcher 一起使用。注意,每个文件的观察者运行事件。因此,您需要在所有事件爆发结束后等待(通过计时器抢劫),并且还建议将 Visual Studio 配置为始终保存为 Ctr+S 组合键。

  3. Microsoft 将 Microsoft.Build.BuildEngine 替换为最新的程序集,并建议与新的 Microsoft.Build 一起使用。我意识到最新的有问题找到最新的工具(MSBuild.exe),如版本 15.0。您可能会收到需要解决方法的错误:Microsoft.Build.Exceptions.InvalidProjectFileException:'工具版本“15.0”无法识别。可用的工具版本为“12.0”、“14.0”、“2.0”、“3.5”、“4.0”。

  4. 动态加载程序集后,Dotnet 无法释放程序集。解决不值得努力。我检查了上面的代码并循环运行它,消耗的 RAM 比我想象的要好。而且,我意识到 Core 3.0 有解决方案。所以如果你喜欢这段代码,我建议你尝试将它迁移到 core 3.0

  5. 表现。我没有在实际项目中尝试过。但如果你有备用内存和强大的 CPU,我相信这对于小型项目来说效果很好,并且延迟不会超过半秒。我将它与代码更改后的开始调试进行比较。Visual Studio 进入调试模式和退出有很长的延迟。所以愿你得到显着的改善。

  6. 顺便说一句,如果您复制调试信息文件(.pdb)以及 lib 文件,VS 将在目标项目发生运行时错误时打开源文件。但是这个文件得到了锁定。然后下一次重新加载失败(这很奇怪,但根据我的检查,如果目标项目是 VB,它就不会发生)。

  7. 如果你真的想用这种方法开发。您需要将目标应用程序构建为小项目的集合。您可以在目标项目中嵌入以下代码,并在开发模式下打开容器窗口。您可以为从文件或外部资源(如数据库)读取的所有数据添加缓存系统。并构建允许将重载直接跳转到界面中的某个点的系统。


推荐阅读