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"
   }
}

参考

WPF点击关闭按钮最小化到系统托盘

包含点击关闭按钮后最小化系统托盘,点击系统托盘图标后自动弹出窗口,点击托盘右键菜单退出


    public partial class MainWindow : Window
    {

        private readonly System.Windows.Forms.NotifyIcon nIcon = new System.Windows.Forms.NotifyIcon();

        public MainWindow()
        {
            InitializeComponent();
            initNotifyIcon();
        }

        private void initNotifyIcon()
        {
            nIcon.Visible = true;
            nIcon.Icon = new Icon("./favicon.ico");
            nIcon.Text = "测试程序";
            nIcon.MouseClick += new System.Windows.Forms.MouseEventHandler(show_Click);
            nIcon.ContextMenu = new System.Windows.Forms.ContextMenu();
            System.Windows.Forms.MenuItem show = new System.Windows.Forms.MenuItem("打开");
            show.Click += new EventHandler(show_Click);
            nIcon.ContextMenu.MenuItems.Add(show);
            System.Windows.Forms.MenuItem exit = new System.Windows.Forms.MenuItem("退出");
            exit.Click += new EventHandler(exit_Click);
            nIcon.ContextMenu.MenuItems.Add(exit);

        }

        private void exit_Click(object sender, EventArgs e)
        {
            Environment.Exit(0);
        }

        private void show_Click(object Sender, EventArgs e)
        {
            if (WindowState == WindowState.Minimized)
                WindowState = WindowState.Normal;
            Show();
            Activate();
        }

        protected override void OnStateChanged(EventArgs e)
        {
            if (WindowState == WindowState.Minimized) Hide();

            base.OnStateChanged(e);
        }

        protected override void OnClosing(CancelEventArgs e)
        {
            e.Cancel = true;
            Hide();
            base.OnClosing(e);
        }

    }

参考

WPF自定义滚动条(含windows和apple两种风格)

测试结构

    <Grid>
        <ListView  VerticalAlignment="Top" HorizontalAlignment="Stretch">
            <ListViewItem>
                <TextBlock Text="xxxx1"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx2"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx3"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx4"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx5"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx6"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx7"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx8"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx9"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx10"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx11"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx12"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx13"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx14"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx15"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx16"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx17"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx18"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx19"/>
            </ListViewItem>
            <ListViewItem>
                <TextBlock Text="xxxx20"/>
            </ListViewItem>
        </ListView>
    </Grid>

Windows 风格

file

<Style x:Key="ScrollBarLineButton"
       TargetType="{x:Type RepeatButton}">
  <Setter Property="SnapsToDevicePixels"
          Value="True" />
  <Setter Property="OverridesDefaultStyle"
          Value="true" />
  <Setter Property="Focusable"
          Value="false" />
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type RepeatButton}">
        <Border x:Name="Border"
                Margin="1"
                CornerRadius="2"
                BorderThickness="1">
          <Border.BorderBrush>
            <LinearGradientBrush StartPoint="0,0"
                                 EndPoint="0,1">
              <LinearGradientBrush.GradientStops>
                <GradientStopCollection>
                  <GradientStop Color="{DynamicResource BorderMediumColor}"
                                Offset="0.0" />
                  <GradientStop Color="{DynamicResource BorderDarkColor}"
                                Offset="1.0" />
                </GradientStopCollection>
              </LinearGradientBrush.GradientStops>
            </LinearGradientBrush>
          </Border.BorderBrush>
          <Border.Background>
            <LinearGradientBrush StartPoint="0,0"
                                 EndPoint="0,1">
              <LinearGradientBrush.GradientStops>
                <GradientStopCollection>
                  <GradientStop Color="{DynamicResource ControlLightColor}"/>
                  <GradientStop Color="{DynamicResource ControlMediumColor}"
                                Offset="1.0" />
                </GradientStopCollection>
              </LinearGradientBrush.GradientStops>
            </LinearGradientBrush>
          </Border.Background>
          <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="CommonStates">
              <VisualState x:Name="Normal" />
              <VisualState x:Name="MouseOver" />
              <VisualState x:Name="Pressed">
                <Storyboard>
                  <ColorAnimationUsingKeyFrames Storyboard.TargetName="Border"
                                                Storyboard.TargetProperty="(Panel.Background).
                    (GradientBrush.GradientStops)[1].(GradientStop.Color)">
                    <EasingColorKeyFrame KeyTime="0"
                                         Value="{StaticResource ControlPressedColor}" />
                  </ColorAnimationUsingKeyFrames>
                </Storyboard>
              </VisualState>
              <VisualState x:Name="Disabled">
                <Storyboard>
                  <ColorAnimationUsingKeyFrames Storyboard.TargetName="Arrow"
                                                Storyboard.TargetProperty="(Shape.Fill).
                    (SolidColorBrush.Color)">
                    <EasingColorKeyFrame KeyTime="0"
                                         Value="{StaticResource DisabledForegroundColor}" />
                  </ColorAnimationUsingKeyFrames>
                </Storyboard>
              </VisualState>
            </VisualStateGroup>
          </VisualStateManager.VisualStateGroups>
          <Path x:Name="Arrow"
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                Data="{Binding Content, 
            RelativeSource={RelativeSource TemplatedParent}}" >
              <Path.Fill>
                  <SolidColorBrush Color="{DynamicResource GlyphColor}"/>
              </Path.Fill>
          </Path>
        </Border>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

<Style x:Key="ScrollBarPageButton"
       TargetType="{x:Type RepeatButton}">
  <Setter Property="SnapsToDevicePixels"
          Value="True" />
  <Setter Property="OverridesDefaultStyle"
          Value="true" />
  <Setter Property="IsTabStop"
          Value="false" />
  <Setter Property="Focusable"
          Value="false" />
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type RepeatButton}">
        <Border Background="Transparent" />
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

<Style x:Key="ScrollBarThumb"
       TargetType="{x:Type Thumb}">
  <Setter Property="SnapsToDevicePixels"
          Value="True" />
  <Setter Property="OverridesDefaultStyle"
          Value="true" />
  <Setter Property="IsTabStop"
          Value="false" />
  <Setter Property="Focusable"
          Value="false" />
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type Thumb}">
        <Border CornerRadius="2"
                Background="{TemplateBinding Background}"
                BorderBrush="{TemplateBinding BorderBrush}"
                BorderThickness="1" />
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

<ControlTemplate x:Key="VerticalScrollBar"
                 TargetType="{x:Type ScrollBar}">
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition MaxHeight="18" />
      <RowDefinition Height="0.00001*" />
      <RowDefinition MaxHeight="18" />
    </Grid.RowDefinitions>
    <Border Grid.RowSpan="3"
            CornerRadius="2"
            Background="#F0F0F0" />
    <RepeatButton Grid.Row="0"
                  Style="{StaticResource ScrollBarLineButton}"
                  Height="18"
                  Command="ScrollBar.LineUpCommand"
                  Content="M 0 4 L 8 4 L 4 0 Z" />
    <Track x:Name="PART_Track"
           Grid.Row="1"
           IsDirectionReversed="true">
      <Track.DecreaseRepeatButton>
        <RepeatButton Style="{StaticResource ScrollBarPageButton}"
                      Command="ScrollBar.PageUpCommand" />
      </Track.DecreaseRepeatButton>
      <Track.Thumb>
        <Thumb Style="{StaticResource ScrollBarThumb}"
               Margin="1,0,1,0">
          <Thumb.BorderBrush>

            <LinearGradientBrush StartPoint="0,0"
                                 EndPoint="1,0">
              <LinearGradientBrush.GradientStops>
                <GradientStopCollection>
                  <GradientStop Color="{DynamicResource BorderLightColor}"
                                Offset="0.0" />
                  <GradientStop Color="{DynamicResource BorderDarkColor}"
                                Offset="1.0" />
                </GradientStopCollection>
              </LinearGradientBrush.GradientStops>
            </LinearGradientBrush>

          </Thumb.BorderBrush>
          <Thumb.Background>

            <LinearGradientBrush StartPoint="0,0"
                                 EndPoint="1,0">
              <LinearGradientBrush.GradientStops>
                <GradientStopCollection>
                  <GradientStop Color="{DynamicResource ControlLightColor}"
                                Offset="0.0" />
                  <GradientStop Color="{DynamicResource ControlMediumColor}"
                                Offset="1.0" />
                </GradientStopCollection>
              </LinearGradientBrush.GradientStops>
            </LinearGradientBrush>

          </Thumb.Background>
        </Thumb>
      </Track.Thumb>
      <Track.IncreaseRepeatButton>
        <RepeatButton Style="{StaticResource ScrollBarPageButton}"
                      Command="ScrollBar.PageDownCommand" />
      </Track.IncreaseRepeatButton>
    </Track>
    <RepeatButton Grid.Row="2"
                  Style="{StaticResource ScrollBarLineButton}"
                  Height="18"
                  Command="ScrollBar.LineDownCommand"
                  Content="M 0 0 L 4 4 L 8 0 Z" />
  </Grid>
</ControlTemplate>

<ControlTemplate x:Key="HorizontalScrollBar"
                 TargetType="{x:Type ScrollBar}">
  <Grid>
    <Grid.ColumnDefinitions>
      <ColumnDefinition MaxWidth="18" />
      <ColumnDefinition Width="0.00001*" />
      <ColumnDefinition MaxWidth="18" />
    </Grid.ColumnDefinitions>
    <Border Grid.ColumnSpan="3"
            CornerRadius="2"
            Background="#F0F0F0" />
    <RepeatButton Grid.Column="0"
                  Style="{StaticResource ScrollBarLineButton}"
                  Width="18"
                  Command="ScrollBar.LineLeftCommand"
                  Content="M 4 0 L 4 8 L 0 4 Z" />
    <Track x:Name="PART_Track"
           Grid.Column="1"
           IsDirectionReversed="False">
      <Track.DecreaseRepeatButton>
        <RepeatButton Style="{StaticResource ScrollBarPageButton}"
                      Command="ScrollBar.PageLeftCommand" />
      </Track.DecreaseRepeatButton>
      <Track.Thumb>
        <Thumb Style="{StaticResource ScrollBarThumb}"
               Margin="0,1,0,1">

          <Thumb.BorderBrush>

            <LinearGradientBrush StartPoint="0,0"
                                 EndPoint="1,0">
              <LinearGradientBrush.GradientStops>
                <GradientStopCollection>
                  <GradientStop Color="{DynamicResource BorderLightColor}"
                                Offset="0.0" />
                  <GradientStop Color="{DynamicResource BorderDarkColor}"
                                Offset="1.0" />
                </GradientStopCollection>
              </LinearGradientBrush.GradientStops>
            </LinearGradientBrush>

          </Thumb.BorderBrush>
          <Thumb.Background>

            <LinearGradientBrush StartPoint="0,0"
                                 EndPoint="0,1">
              <LinearGradientBrush.GradientStops>
                <GradientStopCollection>
                  <GradientStop Color="{DynamicResource ControlLightColor}"
                                Offset="0.0" />
                  <GradientStop Color="{DynamicResource ControlMediumColor}"
                                Offset="1.0" />
                </GradientStopCollection>
              </LinearGradientBrush.GradientStops>
            </LinearGradientBrush>

          </Thumb.Background>
        </Thumb>
      </Track.Thumb>
      <Track.IncreaseRepeatButton>
        <RepeatButton Style="{StaticResource ScrollBarPageButton}"
                      Command="ScrollBar.PageRightCommand" />
      </Track.IncreaseRepeatButton>
    </Track>
    <RepeatButton Grid.Column="2"
                  Style="{StaticResource ScrollBarLineButton}"
                  Width="18"
                  Command="ScrollBar.LineRightCommand"
                  Content="M 0 0 L 4 4 L 0 8 Z" />
  </Grid>
</ControlTemplate>

<Style x:Key="{x:Type ScrollBar}"
       TargetType="{x:Type ScrollBar}">
  <Setter Property="SnapsToDevicePixels"
          Value="True" />
  <Setter Property="OverridesDefaultStyle"
          Value="true" />
  <Style.Triggers>
    <Trigger Property="Orientation"
             Value="Horizontal">
      <Setter Property="Width"
              Value="Auto" />
      <Setter Property="Height"
              Value="18" />
      <Setter Property="Template"
              Value="{StaticResource HorizontalScrollBar}" />
    </Trigger>
    <Trigger Property="Orientation"
             Value="Vertical">
      <Setter Property="Width"
              Value="18" />
      <Setter Property="Height"
              Value="Auto" />
      <Setter Property="Template"
              Value="{StaticResource VerticalScrollBar}" />
    </Trigger>
  </Style.Triggers>
</Style>

需要的资源

<!--Control colors.-->
<Color x:Key="WindowColor">#FFE8EDF9</Color>
<Color x:Key="ContentAreaColorLight">#FFC5CBF9</Color>
<Color x:Key="ContentAreaColorDark">#FF7381F9</Color>

<Color x:Key="DisabledControlLightColor">#FFE8EDF9</Color>
<Color x:Key="DisabledControlDarkColor">#FFC5CBF9</Color>
<Color x:Key="DisabledForegroundColor">#FF888888</Color>

<Color x:Key="SelectedBackgroundColor">#FFC5CBF9</Color>
<Color x:Key="SelectedUnfocusedColor">#FFDDDDDD</Color>

<Color x:Key="ControlLightColor">White</Color>
<Color x:Key="ControlMediumColor">#FF7381F9</Color>
<Color x:Key="ControlDarkColor">#FF211AA9</Color>

<Color x:Key="ControlMouseOverColor">#FF3843C4</Color>
<Color x:Key="ControlPressedColor">#FF211AA9</Color>

<Color x:Key="GlyphColor">#FF444444</Color>
<Color x:Key="GlyphMouseOver">sc#1, 0.004391443, 0.002428215, 0.242281124</Color>

<!--Border colors-->
<Color x:Key="BorderLightColor">#FFCCCCCC</Color>
<Color x:Key="BorderMediumColor">#FF888888</Color>
<Color x:Key="BorderDarkColor">#FF444444</Color>

<Color x:Key="PressedBorderLightColor">#FF888888</Color>
<Color x:Key="PressedBorderDarkColor">#FF444444</Color>

<Color x:Key="DisabledBorderLightColor">#FFAAAAAA</Color>
<Color x:Key="DisabledBorderDarkColor">#FF888888</Color>

<Color x:Key="DefaultBorderBrushDarkColor">Black</Color>

<!--Control-specific resources.-->
<Color x:Key="HeaderTopColor">#FFC5CBF9</Color>
<Color x:Key="DatagridCurrentCellBorderColor">Black</Color>
<Color x:Key="SliderTrackDarkColor">#FFC5CBF9</Color>

<Color x:Key="NavButtonFrameColor">#FF3843C4</Color>

<LinearGradientBrush x:Key="MenuPopupBrush"
                     EndPoint="0.5,1"
                     StartPoint="0.5,0">
  <GradientStop Color="{DynamicResource ControlLightColor}"
                Offset="0" />
  <GradientStop Color="{DynamicResource ControlMediumColor}"
                Offset="0.5" />
  <GradientStop Color="{DynamicResource ControlLightColor}"
                Offset="1" />
</LinearGradientBrush>

<LinearGradientBrush x:Key="ProgressBarIndicatorAnimatedFill"
                     StartPoint="0,0"
                     EndPoint="1,0">
  <LinearGradientBrush.GradientStops>
    <GradientStopCollection>
      <GradientStop Color="#000000FF"
                    Offset="0" />
      <GradientStop Color="#600000FF"
                    Offset="0.4" />
      <GradientStop Color="#600000FF"
                    Offset="0.6" />
      <GradientStop Color="#000000FF"
                    Offset="1" />
    </GradientStopCollection>
  </LinearGradientBrush.GradientStops>
</LinearGradientBrush>

Apple风格

file

<!--Scrollbar Thumbs-->
<Style x:Key="ScrollThumbs" TargetType="{x:Type Thumb}">
<Setter Property="Template">
    <Setter.Value>
        <ControlTemplate TargetType="{x:Type Thumb}">
            <Grid x:Name="Grid">
                <Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Width="Auto" Height="Auto" Fill="Transparent" />
                <Border x:Name="Rectangle1" CornerRadius="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Width="Auto" Height="Auto" Background="{TemplateBinding Background}" />
            </Grid>
            <ControlTemplate.Triggers>
                <Trigger Property="Tag" Value="Horizontal">
                    <Setter TargetName="Rectangle1" Property="Width" Value="Auto" />
                    <Setter TargetName="Rectangle1" Property="Height" Value="7" />
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>
    </Setter.Value>
</Setter>
</Style>
<!--ScrollBars-->
<Style x:Key="{x:Type ScrollBar}" TargetType="{x:Type ScrollBar}">
<Setter Property="Stylus.IsFlicksEnabled" Value="false" />
<Setter Property="Foreground" Value="#8C8C8C" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Width" Value="8" />
<Setter Property="Template">
    <Setter.Value>
        <ControlTemplate TargetType="{x:Type ScrollBar}">
            <Grid x:Name="GridRoot" Width="8" Background="{TemplateBinding Background}">
                <Grid.RowDefinitions>
                    <RowDefinition Height="0.00001*" />
                </Grid.RowDefinitions>
                <Track x:Name="PART_Track" Grid.Row="0" IsDirectionReversed="true" Focusable="false">
                    <Track.Thumb>
                        <Thumb x:Name="Thumb" Background="{TemplateBinding Foreground}" Style="{DynamicResource ScrollThumbs}" />
                    </Track.Thumb>
                    <Track.IncreaseRepeatButton>
                        <RepeatButton x:Name="PageUp" Command="ScrollBar.PageDownCommand" Opacity="0" Focusable="false" />
                    </Track.IncreaseRepeatButton>
                    <Track.DecreaseRepeatButton>
                        <RepeatButton x:Name="PageDown" Command="ScrollBar.PageUpCommand" Opacity="0" Focusable="false" />
                    </Track.DecreaseRepeatButton>
                </Track>
            </Grid>
            <ControlTemplate.Triggers>
                <Trigger SourceName="Thumb" Property="IsMouseOver" Value="true">
                    <Setter Value="{DynamicResource ButtonSelectBrush}" TargetName="Thumb" Property="Background" />
                </Trigger>
                <Trigger SourceName="Thumb" Property="IsDragging" Value="true">
                    <Setter Value="{DynamicResource DarkBrush}" TargetName="Thumb" Property="Background" />
                </Trigger>
                <Trigger Property="IsEnabled" Value="false">
                    <Setter TargetName="Thumb" Property="Visibility" Value="Collapsed" />
                </Trigger>
                <Trigger Property="Orientation" Value="Horizontal">
                    <Setter TargetName="GridRoot" Property="LayoutTransform">
                        <Setter.Value>
                            <RotateTransform Angle="-90" />
                        </Setter.Value>
                    </Setter>
                    <Setter TargetName="PART_Track" Property="LayoutTransform">
                        <Setter.Value>
                            <RotateTransform Angle="-90" />
                        </Setter.Value>
                    </Setter>
                    <Setter Property="Width" Value="Auto" />
                    <Setter Property="Height" Value="8" />
                    <Setter TargetName="Thumb" Property="Tag" Value="Horizontal" />
                    <Setter TargetName="PageDown" Property="Command" Value="ScrollBar.PageLeftCommand" />
                    <Setter TargetName="PageUp" Property="Command" Value="ScrollBar.PageRightCommand" />
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>
    </Setter.Value>
</Setter>
</Style>

参考

Livewire 快速上手教程

Livewire – 不用一行JS实现vue/ng/react等框架的数据绑定效果

理解Livewire

构建现代的Web应用程序很困难。 Vue和React之类的工具功能非常强大,但是它们增加了全栈开发人员工作流程的复杂性。

Livewire是Laravel的全栈框架,可简化构建交互式页面的过程。不用一行JS实现vue/ng/react等框架的数据绑定效果。

理解它的最好方法就是看代码。

下面构建一个搜索组件,当用户键入搜索输入时,用户列表将实时更新。

组件PHP代码:

use Livewire\Component;

class SearchUsers extends Component
{
    public $search = '';

    public function render()
    {
        return view('livewire.search-users', [
            'users' => User::where('username', $this->search)->get(),
        ]);
    }
}

组件视图

<div>
    <input wire:model="search" type="text" placeholder="Search users..."/>

    <ul>
        @foreach($users as $user)
            <li>{{ $user->username }}</li>
        @endforeach
    </ul>
</div>

引用组件

<body>
    ...
    @livewire('search-users')
    ...
</body>

快速上手教程

教程非常简单,客户端点击一次按钮,实现客户端和服务端计数器同时加一。

安装 Livewire

composer require livewire/livewire

在视图中保护livewire的css和js

    @livewireStyles
</head>
<body>
    ...

    @livewireScripts
</body>
</html>

新增组件

php artisan make:livewire counter

组件文件内容

class Counter extends Component
{
    public $count = 0;

    public function increment()
    {
        $this->count++;
    }

    public function render()
    {
        return view('livewire.counter');
    }
}

组件视图

<div style="text-align: center">
    <button wire:click="increment">+</button>
    <h1>{{ $count }}</h1>
</div>

在视图中引用组件

<head>
    ...
    @livewireStyles
</head>
<body>
    <livewire:counter />

    ...

    @livewireScripts
</body>
</html>

相关资源