WPF使用MVVM入门

MVVM 的三个部分

让我们看一下MVVM的三个部分:模型,视图和视图模型。

视图:这些都是UI元素,是应用程序的漂亮面孔。对于WPF,这些都是您的所有XAML文件。它们可能是Windows,用户控件或资源字典。尽管完全可以从代码构造视图当然是可能的,但绝大多数UI将(并且应该)使用XAML构建。该视图可能非常动态,甚至可以处理一些用户交互(请参见下面的“命令”部分)。

视图模型:这些是为每个视图提供数据和功能的对象。通常,视图和视图模型类之间通常存在一对一的映射。视图模型类,将数据公开给视图,并提供处理用户交互的命令。与其他设计模式不同,视图模型不应该知道其视图。关注点的分离是MVVM的主要宗旨之一。视图模型是视图和模型之间的连接。

模型:从广义上讲,模型提供对应用程序所需数据和服务的访问。根据您的应用程序,这是完成实际工作的地方。虽然视图模型与将模型的数据汇总在一起有关,但是模型类执行应用程序的实际工作。如果使用依赖项注入,则通常在视图模型中将模型类作为接口构造函数参数传递。

由于视图模型与模型之间的交互将在很大程度上取决于您的特定应用程序,因此在本文的其余部分中,我们将仅着眼于视图与视图模型之间的交互。

Bidding

绑定引擎使MVVM模式成为可能。绑定在视图中声明,并将视图中的属性链接回视图模型中的属性。

public class ViewModel
{
    public string FirstName { get; set; }
}
<TextBlock Text="{Binding Path=FirstName}" VerticalAlignment="Center" HorizontalAlignment="Center"/>

上面的代码是实现MVVM模式的开始。绑定将Text属性的值设置为FirstName属性中的值。如果要运行此代码,则TextBlock的Text仍为空。这是因为没有将ViewModel类链接到Window的东西。在WPF中,此链接来自DataContext属性。

在Window的构造函数中,我们将设置其DataContext。如果在UI元素上未指定DataContext,它将继承其父级的DataContext。因此,在Window上设置DataContext将有效地为Window中的每个元素设置它。

public MainWindow()
{
    var viewModel = new ViewModel();
    viewModel.FirstName = "Kevin";

    DataContext = viewModel;
    InitializeComponent();

    viewModel.FirstName = "Mark";
}

如果我们运行应用程序,TextBox 仍将显示“ Kevin”,而不是更新后的值“ Mark”。虽然属性的值已经更改,但是没有通知 Binding 更新其值。我们可以通过实现 INotifyPropertyChanged (INPC)接口来解决这个问题。此接口有一个事件,通知绑定,特定属性已更改,使用该事件的任何绑定都应重新计算其值。

我们可以这样实现它:

public class ViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public string FirstName { get; set; }

    public void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

现在,在 Window 的构造函数中,我们通知视图该属性已更改。

public MainWindow()
{
    var viewModel = new ViewModel();
    viewModel.FirstName = "Kevin";

    DataContext = viewModel;
    InitializeComponent();

    viewModel.FirstName = "Mark";
    viewModel.OnPropertyChanged(nameof(ViewModel.FirstName));
}

现在绑定正确地更新以显示“ Mark”。

但是,每次更改属性值时都要记住引发该事件可能会变得非常乏味。因为这种模式非常普遍,许多 MVVM 框架为你的视图模型类提供了一个基类,类似于下面这样:

public abstract class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected bool SetProperty<T>(ref T field, T newValue, [CallerMemberName]string propertyName = null)
    {
        if(!EqualityComparer<T>.Default.Equals(field, newValue))
        {
            field = newValue;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            return true;
        }
        return false;
    }
}

这使我们可以像这样重写FirstName属性:

public class ViewModel : ViewModelBase
{
    private string _firstName;
    public string FirstName
    {
        get => _firstName;
        set => SetProperty(ref _firstName, value);
    }
}

Command

绑定是将数据从视图模型移入视图的一种好方法,但是我们还需要允许我们的视图模型响应用户交互。大多数具有默认用户交互功能(例如单击按钮)的用户控件均由命令处理。所有实现ICommandSource接口的用户控件都支持Command属性,当控件的默认操作发生时,该属性将被调用。有许多实现此接口的控件,例如按钮,菜单项,复选框,单选按钮,超链接等。

命令只是实现ICommand接口的对象。或者换一种说法,命令是从视图到视图模型的消息。当控件的默认事件发生时,例如单击按钮时,将调用命令上的Execute方法。更重要的是,命令还可以指示它们何时能够执行。这允许控件根据是否可以执行其命令来启用或禁用自身。

命令示例

从我们非常简单的示例中可以看出,单击按钮时,我们会更改名字的值。

首先,我们需要在视图模型中添加一个command属性:

public class ViewModel : ViewModelBase
{
    public ICommand ChangeNameCommand { get; }
    ...
}

接下来,我们将向MainWindow添加一个按钮,并使用Binding将其Command属性设置为视图模型中的命令。

<Button Content="Change Name" Command="{Binding Path=ChangeNameCommand}" VerticalAlignment="Bottom" HorizontalAlignment="Center" />

现在,我们只需要向视图模型中的ChangeNameCommand属性分配一个新的命令对象即可。不幸的是,WPF没有附带适合在视图模型中使用的默认ICommand实现,但是该接口非常简单,可以实现:


public class DelegateCommand : ICommand
{
    private readonly Action<object> _executeAction;

    public DelegateCommand(Action<object> executeAction)
    {
        _executeAction = executeAction;
    }

    public void Execute(object parameter) => _executeAction(parameter);

    public bool CanExecute(object parameter) => true;

    public event EventHandler CanExecuteChanged;
}

例如,在这个非常简单的实现中,执行命令时将调用Action委托。现在,我们将忽略接口的CanExecute部分,并始终允许执行命令。

现在,我们可以完成视图模型中的代码。


public class ViewModel : ViewModelBase
{
    ...

    private readonly DelegateCommand _changeNameCommand;
    public ICommand ChangeNameCommand => _changeNameCommand;

    public ViewModel()
    {
        _changeNameCommand = new DelegateCommand(OnChangeName);
    }

    private void OnChangeName(object commandParameter)
    {
        FirstName = "Walter";
    }
}

运行我们的简单应用程序,我们可以看到单击按钮确实可以更改名称。

file

接下来,让我们返回并实现ICommand接口的CanExecute部分。


public class DelegateCommand : ICommand
{
    private readonly Action<object> _executeAction;
    private readonly Func<object, bool> _canExecuteAction;

    public DelegateCommand(Action<object> executeAction, Func<object, bool> canExecuteAction)
    {
        _executeAction = executeAction;
        _canExecuteAction = canExecuteAction;
    }

    public void Execute(object parameter) => _executeAction(parameter);

    public bool CanExecute(object parameter) => _canExecuteAction?.Invoke(parameter) ?? true;

    public event EventHandler CanExecuteChanged;

    public void InvokeCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}

与execute方法类似,此命令还将接受CanExecute委托。同样,CanExecuteChanged事件也使用公共方法公开,因此我们可以在CanExecute委托的返回值更改时随时提高它。

回到我们的视图模型中,我们将进行以下补充。


public ViewModel()
{
    _changeNameCommand = new DelegateCommand(OnChangeName, CanChangeName);
}

private void OnChangeName(object commandParameter)
{
    FirstName = "Walter";
    _changeNameCommand.InvokeCanExecuteChanged();
}

private bool CanChangeName(object commandParameter)
{
    return FirstName != "Walter";
}

调用CanChangeName以确定命令是否可以执行。在这种情况下,一旦名称更改为“ Walter”,我们将仅阻止命令执行。最后,在OnChangeName方法中更改名称后,该命令通过引发其事件来通知按钮其CanExecute状态已更改。

运行该应用程序,我们可以看到在更改名称后该按钮可以正确禁用。

file

参考

使用MailCatcher作为开发环境邮件服务器

MailCatcher 运行一个超级简单的 SMTP 服务器,它可以捕获发送到它的任何消息并在 web 界面中显示。

运行 mailcatcher,将你最喜欢的应用程序设置为 smtp://127.0.0.1:1025,而不是默认的 SMTP 服务器,然后检查 http://127.0.0.1:1080,查看到目前为止的邮件。

file

ubuntu安装MailCatcher

sudo apt install ruby ruby-dev libsqlite3-dev build-essential
sudo gem install mailcatcher

运行

默认运行即可,更多参数如下

mailcatcher -h
Usage: mailcatcher [options]
        --ip IP                      Set the ip address of both servers
        --smtp-ip IP                 Set the ip address of the smtp server
        --smtp-port PORT             Set the port of the smtp server
        --http-ip IP                 Set the ip address of the http server
        --http-port PORT             Set the port address of the http server
        --http-path PATH             Add a prefix to all HTTP paths
        --no-quit                    Don't allow quitting the process
    -f, --foreground                 Run in the foreground
    -v, --verbose                    Be more verbose
    -h, --help                       Display this help information

Cmder + Windows Terminal + PhpStrom/PyCharm/IDEA

Cmder是一个可用于linux的便携式控制台虚拟器,可以让你在windows下使用linux命令。

Windows Terminal是Windows 10的控制台管理界面。

IDEA是广大开发人员的最爱。

本文介绍如何将Cmder整合到Windows Terminal和IDEA。

file

安装Cmder

可以去 https://cmder.net/ 下载最新的压缩文件,解压后点击Cmder.exe即可使用。如果已经安装了git for windows下载mini版本就可以了,否则可以下载包含git的full版本。

设置系统环境变量

以下d:\tools\Cmder是Cmder解压目录

CMDER_ROOT = d:\tools\Cmder

配置 Windows Terminal

生成guid

进入Power shell,输入New-Guid

PS C:\Users\Administrator> New-Guid

Guid
----
092f8948-9ea5-4928-bea7-a1f71938e717

修改 Windows Terminal 配置文件

点击Windows Terminal的下拉菜单,选择“配置”,会打开它的配置文件

增加

{
  "guid": "{092f8948-9ea5-4928-bea7-a1f71938e717}",
  "name": "Cmder",
  "commandline": "cmd.exe /k %CMDER_ROOT%\\vendor\\init.bat",
  "startingDirectory": "%USERPROFILE%",
  "icon": "%CMDER_ROOT%\\icons\\cmder.ico",
  "background": "#2e3436",
  "padding": "15",
  "fontFace": "Cascadia Code",
  "fontSize": 10
}

然后修改

"defaultProfile": "Cmder",

Windows Terminal 使用效果

点击Windows Terminal的加号,查看效果

file

配置 PhpStorm/IDEA/PyCharm

配置 PhpStorm

打开 PhpStrom 的设置,找到 Tools 下的 terminal 修改 Shell path"cmd" /k ""%CMDER_ROOT%\vendor\init.bat""

file

PhpStrom 配置后效果

file

NSIS完整实例(含服务的安装,运行前卸载)


# NetHalt - NSIS installer script
# Copyright (C) 2008 Daniel Collins <solemnwarning@solemnwarning.net>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#    * Redistributions of source code must retain the above copyright
#      notice, this list of conditions and the following disclaimer.
#
#    * Redistributions in binary form must reproduce the above copyright
#      notice, this list of conditions and the following disclaimer in the
#      documentation and/or other materials provided with the distribution.
#
#    * Neither the name of the author nor the names of its contributors may
#      be used to endorse or promote products derived from this software
#      without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

!include MUI2.nsh
!include LogicLib.nsh
!include nsDialogs.nsh

Name "NetHalt"
OutFile "nhclient.exe"

InstallDir $PROGRAMFILES\NetHalt
InstallDirRegKey HKLM "SOFTWARE\NetHalt" "InstallDir"

!define MUI_ABORTWARNING

!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_LICENSE "COPYING"
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES

!insertmacro MUI_LANGUAGE "English"

Function .onInit
    nsisos::osversion

    ${If} $0 < 5
        MessageBox MB_OK "Windows 2000 (NT 5.0) or greater is required"
        Abort
    ${EndIf}
FunctionEnd

# Do local install
#
Section "NetHalt"
    SetShellVarContext all
    SetOutPath $INSTDIR

    # Stop all running nhtray.exe processes
    #
    StrCpy $0 "nhtray.exe"
    KillProc::KillProcesses

    # Check if the NetHalt service is installed
    #
    SimpleSC::ExistsService "nhclient"
    Pop $0

    # Stop+Remove the NetHalt service if it's installed
    #
    ${If} $0 == 0
        DetailPrint "Stopping NetHalt Client service..."
        SimpleSC::StopService "nhclient"

        DetailPrint "Removing NetHalt Client service..."
        SimpleSC::RemoveService "nhclient"
    ${EndIf}

    WriteRegStr HKLM "SOFTWARE\NetHalt" "InstallDir" $INSTDIR

    cinst::reg_write "dword" "SOFTWARE\NetHalt" "use_server" "0"
    cinst::reg_write "string" "SOFTWARE\NetHalt" "server_name" ""
    cinst::reg_write "dword" "SOFTWARE\NetHalt" "server_port" "0"
    cinst::reg_write "dword" "SOFTWARE\NetHalt" "server_refresh" "1800"

    cinst::reg_write "dword" "SOFTWARE\NetHalt" "warning" "300"
    cinst::reg_write "dword" "SOFTWARE\NetHalt" "abort" "0"
    cinst::reg_write "dword" "SOFTWARE\NetHalt" "delay" "0"
    cinst::reg_write "string" "SOFTWARE\NetHalt" "sdtimes" ""

    WriteUninstaller "$INSTDIR\uninstall.exe"

    File "src\nhclient.exe"
    File "src\evlog.dll"
    File "src\nhtray.exe"
    File "src\nhconfig.exe"

    # Add the event log source (evlog.dll) to the registry
    #
    WriteRegStr HKLM "SYSTEM\CurrentControlSet\Services\Eventlog\Application\NetHalt Client" "EventMessageFile" "$INSTDIR\evlog.dll"
    WriteRegDWORD HKLM "SYSTEM\CurrentControlSet\Services\Eventlog\Application\NetHalt Client" "TypesSupported" 0x00000007

    # Add the uninstaller to Add/Remove programs
    #
    WriteRegStr HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\NetHalt" "DisplayName" "NetHalt"
    WriteRegStr HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\NetHalt" "UninstallString" "$INSTDIR\uninstall.exe"
    WriteRegDWORD HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\NetHalt" "NoModify" 1
    WriteRegDWORD HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\NetHalt" "NoRepair" 1

    # Install and start the NetHalt service
    # TODO: Check for errors
    #
    DetailPrint "Installing NetHalt Client service..."
    SimpleSC::InstallService "nhclient" "NetHalt Client" "16" "2" "$INSTDIR\nhclient.exe" "" "" ""

    DetailPrint "Starting NetHalt Client service..."
    SimpleSC::StartService "nhclient"

    # Add nhtray.exe to the registry to run at login
    #
    WriteRegStr HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Run" "NetHalt" "$INSTDIR\nhtray.exe"

    # Launch nhtray.exe
    #
    Exec '"$INSTDIR\nhtray.exe"'

    # Create shortcuts
    #
    CreateDirectory "$SMPROGRAMS\NetHalt"
    CreateShortCut "$SMPROGRAMS\NetHalt\Tray Icon.lnk" "$INSTDIR\nhtray.exe"
    CreateShortCut "$SMPROGRAMS\NetHalt\Configuration.lnk" "$INSTDIR\nhconfig.exe"
    CreateShortCut "$SMPROGRAMS\NetHalt\Uninstall.lnk" "$INSTDIR\uninstall.exe"
SectionEnd

Function un.onInit
    SetShellVarContext all
    ReadRegStr $INSTDIR HKLM "SOFTWARE\NetHalt" "InstallDir"

    MessageBox MB_YESNO "This will uninstall NetHalt, continue?" IDYES NoAbort
    Abort
    NoAbort:
FunctionEnd

Section "Uninstall"
    # Stop and remove the NetHalt service
    #
    DetailPrint "Stopping NetHalt Client service..."
    SimpleSC::StopService "nhclient"

    DetailPrint "Removing NetHalt Client service..."
    SimpleSC::RemoveService "nhclient"

    # Stop all running nhtray.exe processes
    #
    StrCpy $0 "nhtray.exe"
    KillProc::KillProcesses

    # Delete shortcuts
    #
    Delete "$SMPROGRAMS\NetHalt\Tray Icon.lnk"
    Delete "$SMPROGRAMS\NetHalt\Configuration.lnk"
    Delete "$SMPROGRAMS\NetHalt\Un-Install.lnk"
    Delete "$SMPROGRAMS\NetHalt"

    DeleteRegValue HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Run" "NetHalt"
    DeleteRegKey HKLM "SYSTEM\CurrentControlSet\Services\Eventlog\Application\NetHalt Client"
    DeleteRegKey HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\NetHalt"

    Delete "$INSTDIR\nhclient.exe"
    Delete "$INSTDIR\evlog.dll"
    Delete "$INSTDIR\nhtray.exe"
    Delete "$INSTDIR\uninstall.exe"
    RMDir $INSTDIR
SectionEnd

NSIS实现安装前先停止并卸载旧版

以下脚本会先找到正在运行的程序和服务,停止并删除服务,然后杀掉正在运行的程序进程

    ;检查服务是否存在
    SimpleSC::ExistsService  "${SVCHOST_EXE_NAME}"
    Pop $0
    ;停止并删除服务
    ${If} $0 == 0
        DetailPrint "停止正在运行的打印服务"
        SimpleSC::StopService "${SVCHOST_EXE_NAME}" 1 30
        DetailPrint "删除已安装的打印服务"
        SimpleSC::RemoveService "${SVCHOST_EXE_NAME}"
    ${EndIf}
    ;检查主程序是否运行,如果正在运行则进行终止
    nsProcess::_FindProcess "${ANALYST_EXE_NAME}"
    Pop $R0
    ${If} $R0 = 0
      DetailPrint "停止正在运行的主程序"
      nsProcess::_KillProcess "${ANALYST_EXE_NAME}"
      Pop $R0
    ${EndIf}
    ;检查升级程序是否运行,如果正在运行则进行终止
    nsProcess::_FindProcess "${UPDATE_EXE_NAME}"
    Pop $R0
    ${If} $R0 = 0
      DetailPrint "停止正在运行的升级程序"
      nsProcess::_KillProcess "${UPDATE_EXE_NAME}"
      Pop $R0
    ${EndIf}

Django admin export inline sub models

Django 导入导出大部分情况都可以使用django-import-export扩展,但是实际业务中经常需要导出子模型数据,比如导出订单同时需要导出订单商品信息。django-import-export扩展默认不支持导出inline子模型,这时候需要自己实现resource的export方法:

自定义django-import-export的export方法

class OrderResource(resources.ModelResource):
    class Meta:
        model = Order

    def export(self, queryset=None, *args, **kwargs):
        if queryset is None:
            queryset = self.get_queryset()
        ds = tablib.Dataset()
        data = []
        for order in queryset:
            for item in order.items.all():
                row = {
                    'order_id': order.id,
                    "item_id": item.idd,
                }
                data.append(row)
        ds.dict = data
        return ds

参考

JSON、BSON及Protocol Buffer比较

他们核心区别在于,bson在json基础上增加了数据日期、文档类型,并支持二进制传输;而protocol buffer则对传输数据和结构定义进行了分离。一般来说与浏览器通信通常使用json,如果需要跨语言通信,且考虑效率,则可以采用protocol buffer。

JSON

特点:

文本存储和传输,结构与数据一体,肉眼可识别

优点:

  1. 通用性好,各种语言都支持;
  2. 可用于存储

缺点:

  1. 采用文本传输,占用体积大,传输性能差
  2. 序列化,反序列化性能较差
  3. 不支持二进制,日期等数据类型

Bson

特点:

采用二进制传输,结构与数据一体

优点:

  1. 在json基础上增加二进制和日期等格式;
  2. 传输性能较好
  3. 序列化,反序列化性能较好

缺点:

  1. 多语言支持一般
  2. 传输数据与结构定义仍然在一体

Protocol Buffer

特点:

结构定义与传输数据分离,只传输二进制数据

优点:

  • 用protoc工具可以根据proto文件生成各种语言对应代码
  • 字段被编号,新添加的字段不影响老结构,解决了向后兼容问题。
  • 二进制无结构消息,效率高,性能高。

缺点:

  • 二进制格式,可读性差(抓包dump后的数据很难看懂)
  • 默认不具备动态特性

django-q — 比django-celery和django-rq更简单又不失强大的django队列

django-q介绍

Django Q is a native Django task queue, scheduler and worker application using Python multiprocessing.

Features

  • Multiprocessing worker pools
  • Asynchronous tasks
  • Scheduled, cron and repeated tasks
  • Signed and compressed packages
  • Failure and success database or cache
  • Result hooks, groups and chains
  • Django Admin integration
  • PaaS compatible with multiple instances
  • Multi cluster monitor
  • Redis, Disque, IronMQ, SQS, MongoDB or ORM
  • Rollbar and Sentry support

Django Q is tested with: Python 3.7 and 3.8, Django 2.2.x and 3.1.x

安装django-q模块

pip install django-q

增加 django_q 到 INSTALLED_APPS:

修改settings.py

INSTALLED_APPS = (
    # other apps
    'django_q',
)

创建数据库表:

运行 Django migrations

$ python manage.py migrate

配置一个broker

使用django orm数据库作为broker

Q_CLUSTER = {
    'name': 'DjangORM',
    'workers': 1,
    'timeout': 90,
    'retry': 120,
    'queue_limit': 50,
    'bulk': 10,
    'orm': 'default'
}

使用redis作为broker

Q_CLUSTER = {
    'redis': 'redis://h:asdfqwer1234asdf@ec2-111-1-1-1.compute-1.amazonaws.com:111'
}

使用django_redis作为broker

Q_CLUSTER = {
    'name': 'DJRedis',
    'workers': 4,
    'timeout': 90,
    'django_redis': 'default'
}

其他broker: https://django-q.readthedocs.io/en/latest/brokers.html

使用qcluster处理异步任务

python manage.py qcluster

增加异步任务到队列

from django_q.tasks import async_task, result

# create the task
async_task('math.copysign', 2, -2)

# or with import and storing the id
import math.copysign

task_id = async_task(copysign, 2, -2)

# get the result
task_result = result(task_id)

# result returns None if the task has not been executed yet
# you can wait for it
task_result = result(task_id, 200)

# but in most cases you will want to use a hook:

async_task('math.modf', 2.5, hook='hooks.print_result')

# hooks.py
def print_result(task):
    print(task.result)

管理后台页面

Django Q不使用自定义页面,而是默认使用Django模型管理员提供的功能。当您打开Django Q的管理页面时,您将看到三种模型:

成功的任务

意味着他们在执行过程中没有遇到任何错误。

file

失败的任务

失败的任务遇到错误,阻止其完成执行。

计划任务

在这里,您可以检查计划任务的状态,创建,编辑或删除它们。

file

排队的任务

仅当您使用Django ORM代理时才出现

参考

使用AutoUpdater.NET实现WPF和Winfoms项目自动更新

使用AutoUpdater.NET实现自动更新

AutoUpdater.NET是一个.net库,允许winforms和wpf应用实现自动更新,只需要以下几步:

1,定义一个升级文件;

<?xml version="1.0" encoding="UTF-8"?>
<item>
    <version>2.0.0.0</version>
    <url>http://rbsoft.org/downloads/AutoUpdaterTest.zip</url>
    <changelog>https://github.com/ravibpatel/AutoUpdater.NET/releases</changelog>
    <mandatory>false</mandatory>
</item>

2, 安装 Autoupdater.NET.Official扩展

PM> Install-Package Autoupdater.NET.Official

3,使用只需要在程序加载前加入以下代码即可

    protected override void OnStartup(StartupEventArgs e)
        {
            AutoUpdater.ParseUpdateInfoEvent += AutoUpdaterOnParseUpdateInfoEvent;
            //AutoUpdater.ReportErrors = true;
            AutoUpdater.Start("https://api.yunyin.la/file-helper/upgrade");
        }

将xml修改为json

        private void AutoUpdaterOnParseUpdateInfoEvent(ParseUpdateInfoEventArgs args)
        {
            dynamic json = JsonConvert.DeserializeObject(args.RemoteData);
            args.UpdateInfo = new UpdateInfoEventArgs
            {
                CurrentVersion = json.version,
                DownloadURL = json.url,
                Mandatory = new Mandatory
                {
                    Value = json.mandatory.value,
                    UpdateMode = json.mandatory.mode,
                }
            };
        }

json样例

{
   "version":"2.0.0.0",
   "url":"http://rbsoft.org/downloads/AutoUpdaterTest.zip",
   "changelog":"https://github.com/ravibpatel/AutoUpdater.NET/releases",
   "mandatory":{
      "value":true,
      "minVersion": "2.0.0.0",
      "mode":1
   },
   "checksum":{
      "value":"E5F59E50FC91A9E52634FFCB11F32BD37FE0E2F1",
      "hashingAlgorithm":"SHA1"
   }
}

参考