整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

electron创建新窗口(模态框)并相互传值,主进

electron创建新窗口(模态框)并相互传值,主进程传值给子进程

们在开发的过程中难免会遇到需要创建一个子窗口(子进程),但是在这个子进程中所有值都是初始化的,而我们肯定是需要一些值才能进行下一步操作,比如:token; 那么我们怎么去传递值呢?

我先给伙伴们说一些,基本原理(下面很多东西会建立在vue的基础上生命周期,如果是其他框架就自行修改就行),然后再给大家根据代码一步一步操作。

  • ? 首先我们是需要创建一个子进程(或者叫新窗口,模态框都可以)
  • ? 发送一个命令到主进程去创建一个新窗口
  • ? 新窗口创建好以后等页面渲染完成(mounted/this.$nextTick)
  • ? 然后我们在this.$nextTick中通知子进程页面已经渲染完成,
  • ? 子进程接收到消息以后,通知主进程的渲染进程
  • ? 主进程的渲染进程接收到消息以后,携带值发送给主进程,然后发送给子进程的渲染进程
  • ? 到这儿就完成了

大家看到这儿可能觉得很麻烦,但是如果我们在创建新窗口的时候就传递值的话,可能子进程的渲染进程拿不到值;主要原因有:

  • ? 可能子进程还没创建成功,但是值就已经过来了,这个时候你去发送请求啊什么的,就会失败!
  • ? 还有就是可能会出现在创建窗口的时候比较卡顿,你的值过去了,创建还是白面等等

所以我们当时搞了这种方法,目前运行半年以上基本上都没得问题;如果你有更好的方法也可以告诉我们,一起聊聊;哈哈哈

创建新窗口

主进程

在主进程中配置窗口信息,具体内容可以看文档[1],以下是我的配置;配置中的参数都是可以根据自己的需求变化的;

注意: 在开发环境时,root_path的地址必须是你的ip地址,而不是localhost一类的

let modal;
// 接收弹出模态框
ipcMain.on('open-modal',(event,path,title='提示')=>{
    console.log(path);
    let root_path;
    if (process.env.WEBPACK_DEV_SERVER_URL) {
      root_path="http://192.168.110.95:8080/";
      // root_path="http://192.168.124.4:8080/";
    } else {
      // root_path="app://./index.html/";
      root_path=`file://${__dirname}/index.html/`;
    };

    const minWidth=1176;
    const minHeight=600;
    const width=1200;
    const height=700;
    modal=new BrowserWindow({
      parent: BrowserWindow.getFocusedWindow() || undefined,
      modal: true,
    //   frame: false,
      width: width,
      height: height,
      minWidth: minWidth,
      minHeight: minHeight,
    //   autoHideMenuBar: true, // 是否显示菜单栏
      // backgroundColor:'#000', // 背景
      hasShadow: true, // 阴影
      resizable: true, // 窗口是否可以放大
      webPreferences: {
        webviewTag: true,
        contextIsolation: false,
        nodeIntegration: true,
        enableRemoteModule: true,
        webSecurity: false,
      },
    });

    modal.loadURL(root_path + "#/" + path);
});

创建一个路由

{
    path:'/modal',
    name:'modal',
    component:()=> import('@/views/db-file/text.vue'),
},

试试能不能启动

在渲染进程中发送命令,参数需要和路由path一致即可打开窗口

ipcRenderer.send('open-modal','modal')

Snipaste_2023-02-14_11-45-19.png

启动新窗口

当我们做到这的时候,我们的新窗口基本上就算是可以打开了;打开了以后呢!我们需要向他传递一些值,这个时候为了方便区分;如下:

主程序

渲染进程:A渲染进程 主进程:A主进程

子程序(模态框) 渲染进程:B渲染进程 主进程:B主进程

传值

B渲染进程渲染完成以后(vue中的话是nextTick),发送命令通知B主进程

ipcRenderer.send('modal-accomplish')

当在B主进程中接收到消息以后,发送给A渲染进程

 // 通知模态框渲染完成
ipcMain.on('modal-accomplish',(event,msg)=>{
    // 发送给渲染进程
    win.webContents.send("modal-accomplish-state");
})

A渲染进程中接收

onMounted(()=>{
     ipcRenderer.on('modal-accomplish-state',()=>{
        console.log('伟大时代');
     })
})

A渲染进程接收到值以后在发送给A主进程

onMounted(()=>{
     ipcRenderer.on('modal-accomplish-state',()=>{
        console.log('伟大时代');
        ipcRenderer.send('modal-accomplish-end','传值');
     })
})

A主进程接收到值以后发送给B渲染进程

ipcMain.on('modal-accomplish-end',(event,token)=>{
    modal.webContents.send('modal-accomplish-child-end',token);
})

B渲染进程接收值

ipcRenderer.on('modal-accomplish-child-end',(event,msg)=>{
    console.log(msg); // 传值
})

以上五/六步就可以将值获取到了;你学会了吗?

image.png

以上看起来可以能些许复杂,你多练习两次就觉得还好了!这也是为了避免一些问题想出来的;大家可以参考;有更好的方法也可以留言讨论

注意 如果你在写了代码以后没有接收到值的话,可以重启一下;可能是你写了主进程代码更新不及时导致的

战wxPython系列-044

本文介绍如何在Python的GUI工具中嵌入HTML页面。

wxPython的wx.html和wx.html2模块支持解析和显示HTML文件内容。wx.html.HtmlWindow对象是一个通用的HTML浏览器,但不是一个全功能的浏览器,所以支持的功能有限。wx.html.HtmlWindow只支持HTML标准的一个子集,不支持Javascript或者CSS。如果要支持全特性的HTML渲染组件,请使用wx.html2.WebView。

一、wx.html.HtmlWindow简介

wx.html.HtmlWindow控件的目的是显示基于HTML标准字迹的富内容页面(本地文件或者通过HTTP下载的网页文件),一定创建了该窗口控件,可以通过调用SetPage和LoadPage来设置它的内容。

wx.html.HtmlWindow支持的窗口样式

  • wx.html.HW_SCROLLBAR_NEVER:不显示滚动条。
  • wx.html.HW_SCROLLBAR_AUTO:在需要的时候显示滚动条。
  • wx.html.HW_NO_SELECTION:用户不可以选择其中的文本(默认可以)。

wx.html.HtmlWindow发出的事件

  • EVT_HTML_CELL_CLICKED:单击wx.html.HtmlCell时产生。
  • EVT_HTML_CELL_HOVER:鼠标经过一个wx.html.HtmlCell时产生。
  • EVT_HTML_LINK_CLICKED:单击了包含超链接的wx.html.HtmlCell时产生。

wx.html.HtmlWindow常用方法

  • AppendToPage(self, source):将HTML片段附加到当前显示的文本并刷新窗口。
  • GetOpenedAnchor(self):返回当前打开页面中的锚点。如果没有打开任何页,或者所显示的页不是通过调用LoadPage生成的,则返回空字符串。
  • GetOpenedPage(self):返回所打开页面的完整位置。如果没有打开任何页,或者所显示的页不是通过调用LoadPage生成的,则返回空字符串。
  • GetOpenedPageTitle(self):返回所打开页面的标题,如果当前页面不包含< title >标记,则返回“”。
  • HistoryBack(self):移动回上一页。只有使用LoadPage显示的页面才存储在历史列表中。
  • HistoryCanBack(self):如果有可能在历史页面向后操作,即返回True。
  • HistoryCanForward(self):如果有可能在历史中向前操作,即返回True。
  • HistoryClear(self):清除历史页面。
  • HistoryForward(self):移动到下一页。只有使用LoadPage显示的页面才存储在历史列表中。
  • LoadFile(self, filename):从文件加载HTML页面并显示它。
  • LoadPage(self, location):与SetPage不同,这个函数首先从给定的location加载HTML页面,然后显示它。
  • OnCellClicked(self, cell, x, y, event):在wx.html.HtmlWindow内单击鼠标按钮时调用此方法。默认行为是发出wx.html.HtmlCellEvent,如果事件没有被处理或跳过,如果单元格包含超文本链接,则调用OnLinkClicked。
  • OnCellMouseHover(self, cell, x, y):当鼠标移动到HTML单元格上时调用此方法。默认行为是发出wx.html.HtmlCellEvent。
  • OnLinkClicked(self, link):当用户单击超文本链接时调用。默认行为是触发wx.html.HtmlLinkEvent,如果没有处理或跳过该事件,则调用LoadPage,不做任何其他事情。
  • OnOpeningURL(self, type, url):在URL被打开时调用(当用户单击链接或加载图像时)。只有OnOpeningURL返回HTML_OPEN时,URL才会被打开。此方法由wx.html.HtmlParser.OpenURL调用。

图1:wx.html.HtmlWindow类继承关系

二、wx.html.HtmlWindow演示

Htmlwin.py文件

#html窗口(wx.html.HtmlWindow)

import wx
import wx.html

class SampleHtmlWindow(wx.Frame):

    def __init__(self, *args, **kw):
        super(SampleHtmlWindow, self).__init__(*args, **kw)

        self.InitUi()

    def InitUi(self):
        self.SetTitle("实战wxPython: HtmlWindow演示")
        self.SetSize(400, 300)

        panel=wx.Panel(self)
        vbox=wx.BoxSizer(wx.VERTICAL)
        hbox=wx.BoxSizer(wx.HORIZONTAL)

        htmlwin=wx.html.HtmlWindow(panel, wx.ID_ANY, style=wx.NO_BORDER)
        htmlwin.SetStandardFonts()
        htmlwin.LoadPage("page.html")
        """
        htmlwin.SetPage('''
            <html>
                <body bgcolor="#fdd22f">
                    <h1>这是标题</h1>
                    <b style="color:blue">实战wxPython</b>
                    <p>通过RGB值设置背景颜色</p>
                    <br/>
                    <p> 
                        这是一个段落
                    </p>
                    <i>这是斜体文本</i>
                </body>
            </html>
        ''')
        """

        vbox.Add((-1, 10), 0)
        vbox.Add(htmlwin, 1, wx.EXPAND | wx.ALL, 9)

        bitmap=wx.StaticBitmap(panel, wx.ID_ANY, wx.Bitmap("newt.png"))
        hbox.Add(bitmap, 0, wx.LEFT | wx.BOTTOM | wx.TOP, 10)

        btnOk=wx.Button(panel, wx.ID_ANY, "确定")
        self.Bind(wx.EVT_BUTTON, self.OnClose, id=btnOk.GetId())

        hbox.Add((100, -1), 1, wx.LEFT)
        hbox.Add(btnOk, flag=wx.TOP | wx.BOTTOM | wx.RIGHT, border=10)

        vbox.Add(hbox, 0, wx.EXPAND)

        panel.SetSizer(vbox)

        self.Centre()

    def OnClose(self, e):
        self.Close()

def main():
    app=wx.App()
    sample=SampleHtmlWindow(None)
    sample.Show()
    app.MainLoop()

if __name__=="__main__":
    main()

page.html文件

<!DOCTYPE html>
<head>
    <meta charset="UTF-8">
</head>
<html>
    <table cellspacing="5" border="0" width="250">
        <tr width="200" align="left">
            <td bgcolor="#e7e7e7">  Maximum</td>
            <td bgcolor="#aaaaaa">  <b>9000</b></td>
        </tr>
        <tr align="left">
            <td bgcolor="#e7e7e7">  Mean</td>
            <td bgcolor="#aaaaaa">  <b>6076</b></td>
        </tr>
        <tr align="left">
            <td bgcolor="#e7e7e7">  Minimum</td>
            <td bgcolor="#aaaaaa">  <b>3800</b></td>
        </tr>
        <tr align="left">
            <td bgcolor="#e7e7e7">  Median</td>
            <td bgcolor="#aaaaaa">  <b>6000</b></td>
        </tr>
        <tr align="left">
            <td bgcolor="#e7e7e7">  Standard Deviation</td>
            <td bgcolor="#aaaaaa">  <b>6076</b></td>
        </tr>
    </table>
</html>

上面的示例演示了如何使用wx.html.HtmlWindow来加载一个html文件并显示它。

htmlwin=wx.html.HtmlWindow(panel, wx.ID_ANY, style=wx.NO_BORDER)
htmlwin.SetStandardFonts()
htmlwin.LoadPage("page.html")

创建wx.html.HtmlWindow对象,然后使用LoadPage方法加载文件。

图2:wx.html.HtmWindow演示

三、本文知识点

  • 了解wx.html.HtmlWindow控件。
  • 使用wx.html.HtmlWindow加载并显示HTML文件。

前一篇:wxPython - 高级控件之表格Grid

请关注,评论,收藏,点赞,和转发。

很早之前就有写过一个wcPop.js弹窗插件,并且在h5酒店预订、h5聊天室项目中都有使用过,效果还不错。当初想着有时间整合一个Vue版本,刚好趁着国庆节空闲时间捣鼓了个vue.js版自定义模态弹出框组件VPopup

v-popup 一款聚合Msg、Dialog、Popup、ActionSheet、Toast等功能的轻量级移动端Vue弹窗组件。

整合了有赞VantNutUI等热门Vue组件库中Popup弹出层、Toast轻提示、Notify消息提示、Dialog对话框及ActionSheet动作面板等功能。

使用组件

// 在main.js中全局引入
import Vue from 'vue'

import Popup from './components/popup'
Vue.use(Popup)

支持如下两种方式调用组件。

  • 标签式调用
<v-popup
  v-model="showPopup"
  title="标题内容" 
  content="弹窗内容,告知当前状态、信息和解决方法,描述文字尽量控制在三行内"
  type="android"
  shadeClose="false"
  xclose
  z-index="2000"
  :btns="[
    {...},
    {...},
  ]"
/>
  • 函数式调用

this.$vpopup({...}),传入参数即可使用,该函数会返回弹窗组件实例。

let $el=this.$vpopup({
  title: '标题内容',
  content: '弹窗内容,描述文字尽量控制在三行内',
  type: 'android',
  shadeClose: false,
  xclose: true,
  zIndex: 2000,
  btns: [
    {text: '取消'},
    {
      text: '确认',
      style: 'color:#f60;',
      click: ()=> {
        $el.close()
      }
    },
  ]
});

你可根据喜好或项目需要任意选择一种调用方式即可。下面就开始讲解下组件的实现。

在components目录下新建popup.vue页面。

组件参数配置

<!-- Popup 弹出层模板 -->
<template>
  <div v-show="opened" class="nuxt__popup" :class="{'nuxt__popup-closed': closeCls}" :id="id">
    <div v-if="JSON.parse(shade)" class="nuxt__overlay" @click="shadeClicked" :style="{opacity}"></div>
    <div class="nuxt__wrap">
      <div class="nuxt__wrap-section">
        <div class="nuxt__wrap-child" :class="['anim-'+anim, type&&'popui__'+type, round&&'round', position]" :style="popupStyle">
          <div v-if="title" class="nuxt__wrap-tit" v-html="title"></div>
          <div v-if="type=='toast'&&icon" class="nuxt__toast-icon" :class="['nuxt__toast-'+icon]" v-html="toastIcon[icon]"></div>
          <template v-if="$slots.content">
            <div class="nuxt__wrap-cnt"><slot name="content" /></div>
          </template>
          <template v-else>
            <div v-if="content" class="nuxt__wrap-cnt" v-html="content"></div>
          </template>
          <slot />
          <div v-if="btns" class="nuxt__wrap-btns">
            <span v-for="(btn,index) in btns" :key="index" class="btn" :class="{'btn-disabled': btn.disabled}" :style="btn.style" @click="btnClicked($event,index)" v-html="btn.text"></span>
          </div>
          <span v-if="xclose" class="nuxt__xclose" :class="xposition" :style="{'color': xcolor}" @click="close"></span>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
  // 弹窗索引,遮罩次数,定时器
  let $index=0, $lockCount=0, $timer={};
  export default {
    props: {
      ...
    },
    data() {
      return {
        opened: false,
        closeCls: '',
        toastIcon: {
          loading: '<svg viewBox="25 25 50 50"><circle fill="none" cx="50" cy="50" r="20"></circle></svg>',
          success: '<svg viewBox="0 0 1024 1024"><path fill="none" d="M75.712 445.712l240.176 185.52s13.248 6.624 29.808 0l591.36-493.872s84.272-17.968 68.64 71.488c-57.04 57.968-638.464 617.856-638.464 617.856s-38.096 21.536-74.544 0C256.272 790.256 12.816 523.568 12.816 523.568s-6.672-64.592 62.896-77.856z"/></svg>',
          fail: '<svg viewBox="0 0 1024 1024"><path fill="none" d="M450.602 665.598a62.464 62.464 0 0 0 122.88 0l40.96-563.198A102.615 102.615 0 0 0 512.042 0a105.256 105.256 0 0 0-102.4 112.64l40.96 552.958zm61.44 153.6a102.4 102.4 0 1 0 102.4 102.4 96.74 96.74 0 0 0-102.4-102.4z"/></svg>',
        }
      }
    },
    watch: {
      value(val) {
        const type=val ? 'open' : 'close';
        this[type]();
      },
    },
    methods: {
      // 打开弹窗
      open() {
        if(this.opened) return;
        this.opened=true;
        this.$emit('open');
        typeof this.onOpen==='function' && this.onOpen();
        this.$el.style.zIndex=this.getZIndex() + 1;
        
        if(JSON.parse(this.shade)) {
          if(!$lockCount) {
            document.body.classList.add('nt-overflow-hidden');
          }
          $lockCount++;
        }
        
        // 倒计时关闭
        if(this.time) {
          $index++;
          // 防止重复点击
          if($timer[$index] !==null) clearTimeout($timer[$index])
          $timer[$index]=setTimeout(()=> {
            this.close();
          }, parseInt(this.time) * 1000);
        }

        // 长按/右键弹窗
        if(this.follow) {
          // 避免获取不到弹窗宽高
          this.$nextTick(()=> {
            let obj=this.$el.querySelector('.nuxt__wrap-child');
            let oW, oH, winW, winH, pos;

            oW=obj.clientWidth;
            oH=obj.clientHeight;
            winW=window.innerWidth;
            winH=window.innerHeight;
            pos=this.getPos(this.follow[0], this.follow[1], oW, oH, winW, winH);

            obj.style.left=pos[0] + 'px';
            obj.style.top=pos[1] + 'px';
          });
        }
      },
      // 关闭弹窗
      close() {
        if(!this.opened) return;
        
        this.closeCls=true;
        setTimeout(()=> {
          this.opened=false;
          this.closeCls=false;
          if(JSON.parse(this.shade)) {
            $lockCount--;
            if(!$lockCount) {
              document.body.classList.remove('nt-overflow-hidden');
            }
          }
          if(this.time) {
            $index--;
          }
          this.$emit('input', false);
          this.$emit('close');
          typeof this.onClose==='function' && this.onClose();
        }, 200);
      },

      // 点击遮罩层
      shadeClicked() {
        if(JSON.parse(this.shadeClose)) {
          this.close();
        }
      },
      // 按钮事件
      btnClicked(e, index) {
        let btn=this.btns[index];
        if(!btn.disabled) {
          typeof btn.click==='function' && btn.click(e)
        }
      },
      // 获取弹窗层级
      getZIndex() {
        for(var $idx=parseInt(this.zIndex), $el=document.getElementsByTagName('*'), i=0, len=$el.length; i < len; i++)
          $idx=Math.max($idx, $el[i].style.zIndex)
        return $idx;
      },
      // 获取弹窗坐标点
      getPos(x, y, ow, oh, winW, winH) {
        let l=(x + ow) > winW ? x - ow : x;
        let t=(y + oh) > winH ? y - oh : y;
        return [l, t];
      }
    },
  }
</script>

通过监听v-model值调用open和close方法。

watch: {
    value(val) {
        const type=val ? 'open' : 'close';
        this[type]();
    },
},

如果想要实现函数式调用this.$vpopup({...}),则需要使用到Vue.extend扩展实例构造器。

import Vue from 'vue';
import VuePopup from './popup.vue';

let PopupConstructor=Vue.extend(VuePopup);

let $instance;

let VPopup=function(options={}) {
    // 同一个页面中,id相同的Popup的DOM只会存在一个
    options.id=options.id || 'nuxt-popup-id';

    $instance=new PopupConstructor({
        propsData: options
    });

    $instance.vm=$instance.$mount();
    
    let popupDom=document.querySelector('#' + options.id);
    if(options.id && popupDom) {
        popupDom.parentNode.replaceChild($instance.$el, popupDom);
    } else {
        document.body.appendChild($instance.$el);
    }

    Vue.nextTick(()=> {
        $instance.value=true;
    })

    return $instance;
}

VPopup.install=()=> {
    Vue.prototype['$vpopup']=VPopup;
    Vue.component('v-popup', VuePopup);
}

export default VPopup;

这样就实现了引入 Popup 组件后,会自动在 Vue 的 prototype 上挂载 $vpopup 方法和注册 v-popup 组件。

下面就可以愉快的使用标签式及函数式调用组件了。

  • 设置圆角及关闭按钮

<v-popup v-model="showActionPicker" anim="footer" type="actionsheetPicker" round title="标题内容"
    :btns="[
        {text: '取消', click: ()=> showActionPicker=false},
        {text: '确定', style: 'color:#00e0a1;', click: ()=> null},
    ]"
>
    <ul class="goods-list" style="padding:50px;text-align:center;">
        <li>双肩包</li>
        <li>鞋子</li>
        <li>运动裤</li>
    </ul>
</v-popup>

<v-popup v-model="showBottom" position="bottom" round xclose title="标题内容">
    <ul class="goods-list" style="padding:50px;text-align:center;">
        <li>双肩包</li>
        <li>鞋子</li>
        <li>运动裤</li>
    </ul>
</v-popup>
  • 设置按钮禁用状态

按钮设置disabled: true即可禁用按钮事件。

<v-popup v-model="showActionSheet" anim="footer" type="actionsheet" :z-index="2020"
    content="弹窗内容,描述文字尽量控制在三行内"
    :btns="[
        {text: '拍照', style: 'color:#09f;', disabled: true, click: handleInfo},
        {text: '从手机相册选择', style: 'color:#00e0a1;', click: handleInfo},
        {text: '保存图片', style: 'color:#e63d23;', click: ()=> null},
        {text: '取消', click: ()=> showActionSheet=false},
    ]"
/>

另外还支持自定义slot插槽内容,当 content 和 自定义插槽 内容同时存在,只显示插槽内容。

<v-popup v-model="showComponent" xclose xposition="bottom" content="这里是内容信息"
    :btns="[
        {text: '确认', style: 'color:#f60;', click: ()=> showComponent=false},
    ]"
    @open="handleOpen" @close="handleClose"
>
    <template #content>当 content 和 自定义插槽 内容同时存在,只显示插槽内容!</template>
    <div style="padding:30px 15px;">
        <img src="assets/apple3.jpg" style="width:100%;" @click="handleContextPopup" />
    </div>
</v-popup>

好了,就分享到这里。希望对大家有所帮助。目前该组件正在项目中实战测试,后续会分享相关使用情况。