├── .clang-format ├── .gitattributes ├── .gitignore ├── .qmake.stash ├── DemoImages ├── main.png └── sub.png ├── LICENSE ├── Main ├── Main.pro ├── Qml.qrc ├── Qml │ ├── Comp │ │ ├── CButton.qml │ │ └── CDragArea.qml │ └── Main.qml └── Src │ ├── Common.h │ ├── IPC.cpp │ ├── IPC.h │ ├── ProcessMgr.cpp │ ├── ProcessMgr.h │ ├── TabMgr.cpp │ ├── TabMgr.h │ └── main.cpp ├── MulitProcessTab.pro ├── README.md ├── Sub ├── Qml.qrc ├── Qml │ └── Main.qml ├── Src │ └── main.cpp └── Sub.pro ├── appveyor.yml └── common.pri /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | # 语言: None, Cpp, Java, JavaScript, ObjC, Proto, TableGen, TextProto 3 | Language: Cpp 4 | # BasedOnStyle: WebKit 5 | # 访问说明符(public、private等)的偏移 6 | AccessModifierOffset: -4 7 | # 开括号(开圆括号、开尖括号、开方括号)后的对齐: Align, DontAlign, AlwaysBreak(总是在开括号后换行) 8 | AlignAfterOpenBracket: AlwaysBreak 9 | # 连续赋值时,对齐所有等号 10 | AlignConsecutiveAssignments: false 11 | # 连续声明时,对齐所有声明的变量名 12 | AlignConsecutiveDeclarations: false 13 | # 左对齐逃脱换行(使用反斜杠换行)的反斜杠 14 | AlignEscapedNewlines: Right 15 | # 水平对齐二元和三元表达式的操作数 16 | AlignOperands: true 17 | # 对齐连续的尾随的注释 18 | AlignTrailingComments: true 19 | # 允许函数声明的所有参数在放在下一行 20 | AllowAllParametersOfDeclarationOnNextLine: true 21 | # 允许短的块放在同一行 22 | AllowShortBlocksOnASingleLine: false 23 | # 允许短的case标签放在同一行 24 | AllowShortCaseLabelsOnASingleLine: false 25 | # 允许短的函数放在同一行: None, InlineOnly(定义在类中), Empty(空函数), Inline(定义在类中,空函数), All 26 | AllowShortFunctionsOnASingleLine: Empty 27 | # 允许短的if语句保持在同一行 28 | AllowShortIfStatementsOnASingleLine: false 29 | # 允许短的循环保持在同一行 30 | AllowShortLoopsOnASingleLine: false 31 | # 总是在定义返回类型后换行(deprecated) 32 | AlwaysBreakAfterDefinitionReturnType: None 33 | # 总是在返回类型后换行: None, All, TopLevel(顶级函数,不包括在类中的函数), 34 | # AllDefinitions(所有的定义,不包括声明), TopLevelDefinitions(所有的顶级函数的定义) 35 | AlwaysBreakAfterReturnType: None 36 | # 总是在多行string字面量前换行 37 | AlwaysBreakBeforeMultilineStrings: false 38 | # 总是在template声明后换行 39 | AlwaysBreakTemplateDeclarations: true 40 | # false表示函数实参要么都在同一行,要么都各自一行 41 | BinPackArguments: false 42 | # false表示所有形参要么都在同一行,要么都各自一行 43 | BinPackParameters: false 44 | # 大括号换行,只有当BreakBeforeBraces设置为Custom时才有效 45 | BraceWrapping: 46 | # class定义后面 47 | AfterClass: true 48 | # 控制语句后面 49 | AfterControlStatement: false 50 | # enum定义后面 51 | AfterEnum: true 52 | # 函数定义后面 53 | AfterFunction: true 54 | # 命名空间定义后面 55 | AfterNamespace: true 56 | # ObjC定义后面 57 | AfterObjCDeclaration: false 58 | # struct定义后面 59 | AfterStruct: true 60 | # union定义后面 61 | AfterUnion: true 62 | # extern 定义后面 63 | AfterExternBlock: true 64 | # catch之前 65 | BeforeCatch: false 66 | # else 之前 67 | BeforeElse: false 68 | # 缩进大括号 69 | IndentBraces: false 70 | 71 | SplitEmptyFunction: true 72 | 73 | SplitEmptyRecord: true 74 | 75 | SplitEmptyNamespace: true 76 | # 在二元运算符前换行: None(在操作符后换行), NonAssignment(在非赋值的操作符前换行), All(在操作符前换行) 77 | BreakBeforeBinaryOperators: All 78 | # 在大括号前换行: Attach(始终将大括号附加到周围的上下文), Linux(除函数、命名空间和类定义,与Attach类似), 79 | # Mozilla(除枚举、函数、记录定义,与Attach类似), Stroustrup(除函数定义、catch、else,与Attach类似), 80 | # Allman(总是在大括号前换行), GNU(总是在大括号前换行,并对于控制语句的大括号增加额外的缩进), WebKit(在函数前换行), Custom 81 | # 注:这里认为语句块也属于函数 82 | BreakBeforeBraces: Allman 83 | # 继承列表的逗号前换行 84 | BreakBeforeInheritanceComma: false 85 | # 在三元运算符前换行 86 | BreakBeforeTernaryOperators: true 87 | # 在构造函数的初始化列表的逗号前换行 88 | BreakConstructorInitializersBeforeComma: false 89 | # 初始化列表前换行 90 | BreakConstructorInitializers: BeforeComma 91 | # Java注解后换行 92 | BreakAfterJavaFieldAnnotations: false 93 | 94 | BreakStringLiterals: true 95 | # 每行字符的限制,0表示没有限制 96 | ColumnLimit: 160 97 | # 描述具有特殊意义的注释的正则表达式,它不应该被分割为多行或以其它方式改变 98 | CommentPragmas: '^ IWYU pragma:' 99 | # 紧凑 命名空间 100 | CompactNamespaces: false 101 | # 构造函数的初始化列表要么都在同一行,要么都各自一行 102 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 103 | # 构造函数的初始化列表的缩进宽度 104 | ConstructorInitializerIndentWidth: 4 105 | # 延续的行的缩进宽度 106 | ContinuationIndentWidth: 4 107 | # 去除C++11的列表初始化的大括号{后和}前的空格 108 | Cpp11BracedListStyle: false 109 | # 继承最常用的指针和引用的对齐方式 110 | DerivePointerAlignment: false 111 | # 关闭格式化 112 | DisableFormat: false 113 | # 自动检测函数的调用和定义是否被格式为每行一个参数(Experimental) 114 | ExperimentalAutoDetectBinPacking: false 115 | # 固定命名空间注释 116 | FixNamespaceComments: true 117 | # 需要被解读为foreach循环而不是函数调用的宏 118 | ForEachMacros: 119 | - foreach 120 | - Q_FOREACH 121 | - BOOST_FOREACH 122 | 123 | IncludeBlocks: Preserve 124 | # 对#include进行排序,匹配了某正则表达式的#include拥有对应的优先级,匹配不到的则默认优先级为INT_MAX(优先级越小排序越靠前), 125 | # 可以定义负数优先级从而保证某些#include永远在最前面 126 | IncludeCategories: 127 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 128 | Priority: 2 129 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 130 | Priority: 3 131 | - Regex: '.*' 132 | Priority: 1 133 | IncludeIsMainRegex: '(Test)?$' 134 | # 缩进case标签 135 | IndentCaseLabels: true 136 | 137 | IndentPPDirectives: None 138 | # 缩进宽度 139 | IndentWidth: 4 140 | # 函数返回类型换行时,缩进函数声明或函数定义的函数名 141 | IndentWrappedFunctionNames: false 142 | 143 | JavaScriptQuotes: Leave 144 | 145 | JavaScriptWrapImports: true 146 | # 保留在块开始处的空行 147 | KeepEmptyLinesAtTheStartOfBlocks: true 148 | # 开始一个块的宏的正则表达式 149 | MacroBlockBegin: '' 150 | # 结束一个块的宏的正则表达式 151 | MacroBlockEnd: '' 152 | # 连续空行的最大数量 153 | MaxEmptyLinesToKeep: 1 154 | 155 | # 命名空间的缩进: None, Inner(缩进嵌套的命名空间中的内容), All 156 | NamespaceIndentation: Inner 157 | # 使用ObjC块时缩进宽度 158 | ObjCBlockIndentWidth: 4 159 | # 在ObjC的@property后添加一个空格 160 | ObjCSpaceAfterProperty: true 161 | # 在ObjC的protocol列表前添加一个空格 162 | ObjCSpaceBeforeProtocolList: true 163 | 164 | PenaltyBreakAssignment: 2 165 | 166 | PenaltyBreakBeforeFirstCallParameter: 19 167 | # 在一个注释中引入换行的penalty 168 | PenaltyBreakComment: 300 169 | # 第一次在<<前换行的penalty 170 | PenaltyBreakFirstLessLess: 120 171 | # 在一个字符串字面量中引入换行的penalty 172 | PenaltyBreakString: 1000 173 | # 对于每个在行字符数限制之外的字符的penalty 174 | PenaltyExcessCharacter: 1000000 175 | # 将函数的返回类型放到它自己的行的penalty 176 | PenaltyReturnTypeOnItsOwnLine: 60 177 | # 指针和引用的对齐: Left, Right, Middle 178 | PointerAlignment: Right 179 | 180 | #RawStringFormats: 181 | # - Delimiter: pb 182 | # Language: TextProto 183 | # BasedOnStyle: google 184 | # 允许重新排版注释 185 | ReflowComments: true 186 | # 允许排序#include 187 | SortIncludes: true 188 | 189 | SortUsingDeclarations: true 190 | # 在C风格类型转换后添加空格 191 | SpaceAfterCStyleCast: false 192 | # 模板关键字后面添加空格 193 | SpaceAfterTemplateKeyword: true 194 | # 在赋值运算符之前添加空格 195 | SpaceBeforeAssignmentOperators: true 196 | # 开圆括号之前添加一个空格: Never, ControlStatements, Always 197 | SpaceBeforeParens: ControlStatements 198 | # 在空的圆括号中添加空格 199 | SpaceInEmptyParentheses: false 200 | # 在尾随的评论前添加的空格数(只适用于//) 201 | SpacesBeforeTrailingComments: 1 202 | # 在尖括号的<后和>前添加空格 203 | SpacesInAngles: false 204 | # 在容器(ObjC和JavaScript的数组和字典等)字面量中添加空格 205 | SpacesInContainerLiterals: true 206 | # 在C风格类型转换的括号中添加空格 207 | SpacesInCStyleCastParentheses: false 208 | # 在圆括号的(后和)前添加空格 209 | SpacesInParentheses: false 210 | # 在方括号的[后和]前添加空格,lamda表达式和未指明大小的数组的声明不受影响 211 | SpacesInSquareBrackets: false 212 | # 标准: Cpp03, Cpp11, Auto 213 | Standard: Cpp11 214 | # tab宽度 215 | TabWidth: 4 216 | # 使用tab字符: Never, ForIndentation, ForContinuationAndIndentation, Always 217 | UseTab: Never 218 | ... 219 | 220 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | build/ 5 | run/ 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # DNX 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | 50 | *_i.c 51 | *_p.c 52 | *_i.h 53 | *.ilk 54 | *.meta 55 | *.obj 56 | *.pch 57 | *.pdb 58 | *.pgc 59 | *.pgd 60 | *.rsp 61 | *.sbr 62 | *.tlb 63 | *.tli 64 | *.tlh 65 | *.tmp 66 | *.tmp_proj 67 | *.log 68 | *.vspscc 69 | *.vssscc 70 | .builds 71 | *.pidb 72 | *.svclog 73 | *.scc 74 | 75 | # Chutzpah Test files 76 | _Chutzpah* 77 | 78 | # Visual C++ cache files 79 | ipch/ 80 | *.aps 81 | *.ncb 82 | *.opendb 83 | *.opensdf 84 | *.sdf 85 | *.cachefile 86 | *.VC.db 87 | *.VC.VC.opendb 88 | 89 | # Visual Studio profiler 90 | *.psess 91 | *.vsp 92 | *.vspx 93 | *.sap 94 | 95 | # TFS 2012 Local Workspace 96 | $tf/ 97 | 98 | # Guidance Automation Toolkit 99 | *.gpState 100 | 101 | # ReSharper is a .NET coding add-in 102 | _ReSharper*/ 103 | *.[Rr]e[Ss]harper 104 | *.DotSettings.user 105 | 106 | # JustCode is a .NET coding add-in 107 | .JustCode 108 | 109 | # TeamCity is a build add-in 110 | _TeamCity* 111 | 112 | # DotCover is a Code Coverage Tool 113 | *.dotCover 114 | 115 | # NCrunch 116 | _NCrunch_* 117 | .*crunch*.local.xml 118 | nCrunchTemp_* 119 | 120 | # MightyMoose 121 | *.mm.* 122 | AutoTest.Net/ 123 | 124 | # Web workbench (sass) 125 | .sass-cache/ 126 | 127 | # Installshield output folder 128 | [Ee]xpress/ 129 | 130 | # DocProject is a documentation generator add-in 131 | DocProject/buildhelp/ 132 | DocProject/Help/*.HxT 133 | DocProject/Help/*.HxC 134 | DocProject/Help/*.hhc 135 | DocProject/Help/*.hhk 136 | DocProject/Help/*.hhp 137 | DocProject/Help/Html2 138 | DocProject/Help/html 139 | 140 | # Click-Once directory 141 | publish/ 142 | 143 | # Publish Web Output 144 | *.[Pp]ublish.xml 145 | *.azurePubxml 146 | # TODO: Comment the next line if you want to checkin your web deploy settings 147 | # but database connection strings (with potential passwords) will be unencrypted 148 | #*.pubxml 149 | *.publishproj 150 | 151 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 152 | # checkin your Azure Web App publish settings, but sensitive information contained 153 | # in these scripts will be unencrypted 154 | PublishScripts/ 155 | 156 | # NuGet Packages 157 | *.nupkg 158 | # The packages folder can be ignored because of Package Restore 159 | **/packages/* 160 | # except build/, which is used as an MSBuild target. 161 | !**/packages/build/ 162 | # Uncomment if necessary however generally it will be regenerated when needed 163 | #!**/packages/repositories.config 164 | # NuGet v3's project.json files produces more ignoreable files 165 | *.nuget.props 166 | *.nuget.targets 167 | 168 | # Microsoft Azure Build Output 169 | csx/ 170 | *.build.csdef 171 | 172 | # Microsoft Azure Emulator 173 | ecf/ 174 | rcf/ 175 | 176 | # Windows Store app package directories and files 177 | AppPackages/ 178 | BundleArtifacts/ 179 | Package.StoreAssociation.xml 180 | _pkginfo.txt 181 | 182 | # Visual Studio cache files 183 | # files ending in .cache can be ignored 184 | *.[Cc]ache 185 | # but keep track of directories ending in .cache 186 | !*.[Cc]ache/ 187 | 188 | # Others 189 | ClientBin/ 190 | ~$* 191 | *~ 192 | *.dbmdl 193 | *.dbproj.schemaview 194 | *.jfm 195 | *.pfx 196 | *.publishsettings 197 | node_modules/ 198 | orleans.codegen.cs 199 | 200 | # Since there are multiple workflows, uncomment next line to ignore bower_components 201 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 202 | #bower_components/ 203 | 204 | # RIA/Silverlight projects 205 | Generated_Code/ 206 | 207 | # Backup & report files from converting an old project file 208 | # to a newer Visual Studio version. Backup files are not needed, 209 | # because we have git ;-) 210 | _UpgradeReport_Files/ 211 | Backup*/ 212 | UpgradeLog*.XML 213 | UpgradeLog*.htm 214 | 215 | # SQL Server files 216 | *.mdf 217 | *.ldf 218 | 219 | # Business Intelligence projects 220 | *.rdl.data 221 | *.bim.layout 222 | *.bim_*.settings 223 | 224 | # Microsoft Fakes 225 | FakesAssemblies/ 226 | 227 | # GhostDoc plugin setting file 228 | *.GhostDoc.xml 229 | 230 | # Node.js Tools for Visual Studio 231 | .ntvs_analysis.dat 232 | 233 | # Visual Studio 6 build log 234 | *.plg 235 | 236 | # Visual Studio 6 workspace options file 237 | *.opt 238 | 239 | # Visual Studio LightSwitch build output 240 | **/*.HTMLClient/GeneratedArtifacts 241 | **/*.DesktopClient/GeneratedArtifacts 242 | **/*.DesktopClient/ModelManifest.xml 243 | **/*.Server/GeneratedArtifacts 244 | **/*.Server/ModelManifest.xml 245 | _Pvt_Extensions 246 | 247 | # Paket dependency manager 248 | .paket/paket.exe 249 | paket-files/ 250 | 251 | # FAKE - F# Make 252 | .fake/ 253 | 254 | # JetBrains Rider 255 | .idea/ 256 | *.sln.iml 257 | 258 | # CodeRush 259 | .cr/ 260 | 261 | # Python Tools for Visual Studio (PTVS) 262 | __pycache__/ 263 | *.pyc 264 | /.vscode/settings.json 265 | -------------------------------------------------------------------------------- /.qmake.stash: -------------------------------------------------------------------------------- 1 | QMAKE_LICHECK_TIMESTAMP = "Tue Oct 16" 2 | QMAKE_CXX.INCDIRS = \ 3 | "C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\INCLUDE" \ 4 | "C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\ATLMFC\\INCLUDE" \ 5 | "C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.17134.0\\ucrt" \ 6 | "C:\\Program Files (x86)\\Windows Kits\\NETFXSDK\\4.6.1\\include\\um" \ 7 | "C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.17134.0\\shared" \ 8 | "C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.17134.0\\um" \ 9 | "C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.17134.0\\winrt" 10 | QMAKE_CXX.LIBDIRS = \ 11 | "C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\LIB\\amd64" \ 12 | "C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\ATLMFC\\LIB\\amd64" \ 13 | "C:\\Program Files (x86)\\Windows Kits\\10\\lib\\10.0.17134.0\\ucrt\\x64" \ 14 | "C:\\Program Files (x86)\\Windows Kits\\NETFXSDK\\4.6.1\\lib\\um\\x64" \ 15 | "C:\\Program Files (x86)\\Windows Kits\\10\\lib\\10.0.17134.0\\um\\x64" 16 | QMAKE_CXX.QT_COMPILER_STDCXX = 199711L 17 | QMAKE_CXX.QMAKE_MSC_VER = 1900 18 | QMAKE_CXX.QMAKE_MSC_FULL_VER = 190024215 19 | QMAKE_CXX.COMPILER_MACROS = \ 20 | QT_COMPILER_STDCXX \ 21 | QMAKE_MSC_VER \ 22 | QMAKE_MSC_FULL_VER 23 | -------------------------------------------------------------------------------- /DemoImages/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredtao/MulitProcessTab/2ea82eb144836778c9af21a6e8d9331bba5ac051/DemoImages/main.png -------------------------------------------------------------------------------- /DemoImages/sub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredtao/MulitProcessTab/2ea82eb144836778c9af21a6e8d9331bba5ac051/DemoImages/sub.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 贾文涛 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Main/Main.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | TARGET= Main 3 | include($$PWD/../common.pri) 4 | LIBS += -luser32 5 | SOURCES += \ 6 | Src/main.cpp \ 7 | Src/TabMgr.cpp \ 8 | Src/ProcessMgr.cpp \ 9 | Src/IPC.cpp 10 | 11 | RESOURCES += \ 12 | Qml.qrc 13 | 14 | HEADERS += \ 15 | Src/TabMgr.h \ 16 | Src/ProcessMgr.h \ 17 | Src/IPC.h \ 18 | Src/Common.h 19 | -------------------------------------------------------------------------------- /Main/Qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | Qml/Main.qml 4 | Qml/Comp/CButton.qml 5 | Qml/Comp/CDragArea.qml 6 | 7 | 8 | -------------------------------------------------------------------------------- /Main/Qml/Comp/CButton.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.9 2 | import QtQuick.Controls 2.2 3 | Rectangle { 4 | id: root 5 | implicitWidth: 50 6 | implicitHeight: 40 7 | property alias text: btnText.text 8 | signal clicked() 9 | color: btnArea.pressed ? "red" : (btnArea.containsMouse ? "darkGray" : "white") 10 | property alias pressed: btnArea.pressed 11 | property alias containsMouse: btnArea.containsMouse 12 | Text { 13 | id: btnText 14 | anchors.centerIn: parent 15 | } 16 | MouseArea { 17 | id: btnArea 18 | anchors.fill: parent 19 | hoverEnabled: true 20 | onClicked: root.clicked() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Main/Qml/Comp/CDragArea.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.9 2 | 3 | MouseArea { 4 | anchors.fill: parent 5 | hoverEnabled: true 6 | property real lastX: 0 7 | property real lastY: 0 8 | property var dragTarget 9 | onContainsMouseChanged: { 10 | if (containsMouse) { 11 | cursorShape = Qt.OpenHandCursor 12 | } else { 13 | cursorShape = Qt.ArrowCursor 14 | } 15 | } 16 | onPressedChanged: { 17 | if (containsPress) { 18 | lastX = mouseX; 19 | lastY = mouseY; 20 | } 21 | } 22 | onPositionChanged: { 23 | if (pressed && dragTarget) { 24 | dragTarget.x += mouseX - lastX 25 | dragTarget.y += mouseY - lastY 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Main/Qml/Main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.9 2 | import QtQuick.Controls 2.2 3 | import QtQuick.Layouts 1.3 4 | import "./Comp" 5 | Item { 6 | id: root 7 | Rectangle { 8 | id: titleRect 9 | objectName: "titleRect" 10 | width: parent.width 11 | height: 40 12 | color: "blue" 13 | CDragArea { 14 | anchors.fill: parent 15 | dragTarget: rootView 16 | } 17 | CButton { 18 | id: mainPageBtn 19 | text: "首页" 20 | width: 150 21 | anchors.left: parent.left 22 | color: pressed ? "red" : 23 | (tabMgr.currentTab === "main" ? "green" : 24 | (containsMouse ? "darkGray" : "white")) 25 | onClicked: { 26 | tabMgr.activeTab("main") 27 | } 28 | } 29 | ListView { 30 | id: tabListView 31 | height: parent.height 32 | anchors.left: mainPageBtn.right 33 | anchors.right: toolBtnRow.left 34 | model: tabMgr.tabList 35 | orientation: Qt.Horizontal 36 | interactive: false; 37 | delegate: CButton { 38 | height: tabListView.height 39 | width: tabListView.count > 4 ? tabListView.width / tabListView.count : 150 40 | color: pressed ? "red" : ( tabMgr.currentTab === modelData ? "green": 41 | (containsMouse ? "darkGray" : "white")) 42 | text: modelData 43 | onClicked: { 44 | tabMgr.activeTab(modelData) 45 | } 46 | CButton { 47 | width: 8 48 | height: 8 49 | anchors.right: parent.right 50 | anchors.top: parent.top 51 | text: "X" 52 | color: pressed ? "red" : "gray" 53 | onClicked: { 54 | tabMgr.closeTab(modelData) 55 | } 56 | } 57 | } 58 | } 59 | Row { 60 | id: toolBtnRow 61 | anchors.right: parent.right 62 | CButton { 63 | text: "一" 64 | onClicked: { 65 | tabMgr.showMinimized() 66 | } 67 | } 68 | CButton { 69 | text: "口" 70 | property bool isMaxed: false 71 | onClicked: { 72 | if (isMaxed) 73 | { 74 | tabMgr.showNormal(); 75 | } 76 | else 77 | { 78 | tabMgr.showMaximized(); 79 | } 80 | 81 | isMaxed = ! isMaxed; 82 | } 83 | } 84 | CButton { 85 | text: "X" 86 | onClicked: { 87 | Qt.quit() 88 | } 89 | } 90 | } 91 | 92 | } 93 | Rectangle { 94 | id: contentRect 95 | objectName: "contentRect" 96 | width: parent.width 97 | anchors.top: titleRect.bottom 98 | anchors.bottom: parent.bottom 99 | color: "gray" 100 | GridLayout { 101 | anchors.fill: parent 102 | anchors.margins: 20 103 | columns: 4 104 | rows: 4 105 | Repeater { 106 | model: 16 107 | CButton { 108 | implicitWidth: 200 109 | implicitHeight: 150 110 | color: randColor() 111 | text: index 112 | onClicked: { 113 | tabMgr.activeTab(index) 114 | } 115 | } 116 | } 117 | } 118 | 119 | } 120 | function randColor() { 121 | return Qt.rgba(Math.random(), Math.random(), Math.random(), 1) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /Main/Src/Common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace std { 6 | template<> 7 | struct hash 8 | { 9 | std::size_t operator ()(const QString &str) const noexcept 10 | { 11 | return qHash(str); 12 | } 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /Main/Src/IPC.cpp: -------------------------------------------------------------------------------- 1 | #include "IPC.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | IPC::IPC(QObject *parent) : QObject(parent) 7 | { 8 | } 9 | 10 | IPC::~IPC() 11 | { 12 | m_server.close(); 13 | m_socketMap.clear(); 14 | } 15 | 16 | void IPC::Init() 17 | { 18 | connect(&m_server, &QLocalServer::newConnection, this, &IPC::onNewConnection); 19 | m_server.listen(QUuid::createUuid().toString()); 20 | } 21 | 22 | void IPC::sendData(const QString &socketName, const QByteArray &data) 23 | { 24 | auto itor = m_socketMap.find(socketName); 25 | if (itor != m_socketMap.end()) 26 | { 27 | itor->second->write(data); 28 | } 29 | } 30 | 31 | void IPC::onNewConnection() 32 | { 33 | while (m_server.hasPendingConnections()) 34 | { 35 | QLocalSocket *socket = m_server.nextPendingConnection(); 36 | connect(socket, QOverload::of(&QLocalSocket::error), this, &IPC::onError); 37 | connect(socket, &QLocalSocket::readyRead, this, &IPC::onReadyRead); 38 | connect(socket, &QLocalSocket::disconnected, this, &IPC::onDisconnected); 39 | //这里不直接存储socket指针,收到第一次数据时,才保存 40 | } 41 | } 42 | 43 | void IPC::onError(QLocalSocket::LocalSocketError socketError) 44 | { 45 | Q_UNUSED(socketError) 46 | QLocalSocket *socket = qobject_cast(sender()); 47 | Q_ASSERT(socket); 48 | qWarning() << "socket error " << socket->errorString(); 49 | } 50 | 51 | void IPC::onReadyRead() 52 | { 53 | QLocalSocket *socket = qobject_cast(sender()); 54 | Q_ASSERT(socket); 55 | QByteArray data = socket->readAll(); 56 | if (socket->objectName().isEmpty()) 57 | { 58 | QJsonDocument doc = QJsonDocument::fromJson(data); 59 | QJsonObject obj = doc.object(); 60 | if (obj.contains("processName")) 61 | { 62 | QString socketName = obj["processName"].toString(); 63 | socket->setObjectName(socketName); 64 | m_socketMap.insert({socketName, socket}); 65 | } 66 | } 67 | else 68 | { 69 | emit readyRead(socket->objectName(), data); 70 | } 71 | } 72 | 73 | void IPC::onDisconnected() 74 | { 75 | QLocalSocket *socket = qobject_cast(sender()); 76 | Q_ASSERT(socket); 77 | qInfo() << "socket disconnected"; 78 | auto itor = m_socketMap.find(socket->objectName()); 79 | if (itor != m_socketMap.end()) 80 | { 81 | m_socketMap.erase(itor); 82 | } 83 | // socket->deleteLater(); 84 | } 85 | -------------------------------------------------------------------------------- /Main/Src/IPC.h: -------------------------------------------------------------------------------- 1 | #ifndef IPC_H 2 | #define IPC_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "Common.h" 9 | class IPC : public QObject 10 | { 11 | Q_OBJECT 12 | public: 13 | explicit IPC(QObject *parent = nullptr); 14 | ~IPC(); 15 | //初始化 16 | void Init(); 17 | QString GetServerName() const 18 | { 19 | return m_server.serverName(); 20 | } 21 | void sendData(const QString &socketName, const QByteArray &data); 22 | signals: 23 | void readyRead(const QString &socketName, const QByteArray &data); 24 | private slots: 25 | void onNewConnection(); 26 | void onError(QLocalSocket::LocalSocketError socketError); 27 | void onReadyRead(); 28 | void onDisconnected(); 29 | private: 30 | QLocalServer m_server; 31 | std::unordered_map m_socketMap; 32 | }; 33 | 34 | #endif // IPC_H 35 | -------------------------------------------------------------------------------- /Main/Src/ProcessMgr.cpp: -------------------------------------------------------------------------------- 1 | #include "ProcessMgr.h" 2 | #include 3 | #include 4 | 5 | ProcessMgr::ProcessMgr(QObject *parent) : QObject(parent) {} 6 | 7 | ProcessMgr::~ProcessMgr() {} 8 | 9 | void ProcessMgr::createProcess(const QString &program, const QStringList &arguments) 10 | { 11 | //创建新的QProcess,注意设置parent为this(可以让指针自动释放;可以在当前进程结束时,让子进程自动关闭)。 12 | QProcess *pro = new QProcess(this); 13 | pro->setProcessEnvironment(QProcessEnvironment::systemEnvironment()); 14 | pro->setWorkingDirectory(qApp->applicationDirPath()); 15 | connectProcess(pro); 16 | pro->start(program, arguments); 17 | pro->waitForStarted(); 18 | } 19 | 20 | void ProcessMgr::connectProcess(QProcess *process) 21 | { 22 | connect(process, &QProcess::errorOccurred, this, &ProcessMgr::onErrorOccurred); 23 | connect(process, &QProcess::readyReadStandardOutput, this, &ProcessMgr::onReadyReadStandardOutput); 24 | connect(process, &QProcess::readyReadStandardError, this, &ProcessMgr::onReadyReadStandardError); 25 | connect(process, QOverload::of(&QProcess::finished), this, &ProcessMgr::onFinished); 26 | } 27 | 28 | void ProcessMgr::onFinished(int exitCode) 29 | { 30 | QProcess *pro = qobject_cast(sender()); 31 | Q_ASSERT(pro); 32 | qInfo() << pro->program() << pro->processId() << " finished with exitcode " << exitCode; 33 | pro->deleteLater(); 34 | } 35 | 36 | void ProcessMgr::onErrorOccurred(QProcess::ProcessError error) 37 | { 38 | Q_UNUSED(error) 39 | QProcess *pro = qobject_cast(sender()); 40 | Q_ASSERT(pro); 41 | qWarning() << pro->program() << pro->processId() << " error: " << pro->errorString(); 42 | } 43 | 44 | void ProcessMgr::onReadyReadStandardOutput() 45 | { 46 | QProcess *pro = qobject_cast(sender()); 47 | Q_ASSERT(pro); 48 | qInfo() << pro->program() << pro->processId() << pro->readAllStandardOutput(); 49 | } 50 | 51 | void ProcessMgr::onReadyReadStandardError() 52 | { 53 | QProcess *pro = qobject_cast(sender()); 54 | Q_ASSERT(pro); 55 | qWarning() << pro->program() << pro->processId() << pro->readAllStandardError(); 56 | } 57 | -------------------------------------------------------------------------------- /Main/Src/ProcessMgr.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | class ProcessMgr : public QObject 6 | { 7 | Q_OBJECT 8 | public: 9 | explicit ProcessMgr(QObject *parent = nullptr); 10 | ~ProcessMgr(); 11 | void createProcess(const QString &program, const QStringList &arguments); 12 | signals: 13 | 14 | public slots: 15 | 16 | private slots: 17 | void onFinished(int exitCode); 18 | void onErrorOccurred(QProcess::ProcessError error); 19 | void onReadyReadStandardOutput(); 20 | void onReadyReadStandardError(); 21 | private: 22 | void connectProcess(QProcess *process); 23 | 24 | }; 25 | -------------------------------------------------------------------------------- /Main/Src/TabMgr.cpp: -------------------------------------------------------------------------------- 1 | #include "TabMgr.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | std::string GetLastErrorAsString() 16 | { 17 | // Get the error message, if any. 18 | DWORD errorMessageID = ::GetLastError(); 19 | if (errorMessageID == 0) 20 | return std::string("errorMessageID is 0"); // No error message has been recorded 21 | 22 | LPSTR messageBuffer = nullptr; 23 | size_t size = ::FormatMessageA( 24 | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 25 | nullptr, 26 | errorMessageID, 27 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 28 | (LPSTR)(&messageBuffer), 29 | 0, 30 | nullptr); 31 | std::string message(messageBuffer, size); 32 | 33 | // Free the buffer. 34 | ::LocalFree(messageBuffer); 35 | 36 | return message; 37 | } 38 | 39 | TabMgr::TabMgr(QObject *parent) : QObject(parent) 40 | { 41 | m_view.resize(1200, 800); 42 | m_view.setResizeMode(QQuickView::SizeRootObjectToView); 43 | m_view.setFlags(Qt::FramelessWindowHint | Qt::Dialog); 44 | m_view.rootContext()->setContextProperty("rootView", &m_view); 45 | m_view.rootContext()->setContextProperty("tabMgr", this); 46 | m_view.setSource(QUrl("qrc:/Qml/Main.qml")); 47 | connect(&m_view, &QQuickView::widthChanged, this, &TabMgr::syncSize); 48 | connect(&m_view, &QQuickView::heightChanged, this, &TabMgr::syncSize); 49 | connect(m_view.engine(), &QQmlEngine::quit, qApp, &QApplication::quit); 50 | m_view.show(); 51 | m_ipc.Init(); 52 | connect(&m_ipc, &IPC::readyRead, this, &TabMgr::onReadyReay); 53 | } 54 | 55 | TabMgr::~TabMgr() 56 | { 57 | for (auto name : m_tabList) 58 | { 59 | closeTab(name); 60 | } 61 | } 62 | 63 | void TabMgr::activeTab(const QString &name) 64 | { 65 | if (name == s_mainStr) 66 | { 67 | setCurrentTab(name); 68 | return; 69 | } 70 | auto it = m_processMap.find(name); 71 | if (it == m_processMap.end()) 72 | { 73 | //找不到则创建进程 74 | QStringList args; 75 | args << m_ipc.GetServerName() << name; 76 | { 77 | QQuickItem *titleItem = m_view.rootObject()->findChild("titleRect"); 78 | Q_ASSERT(titleItem); 79 | QQuickItem *contentItem = m_view.rootObject()->findChild("contentRect"); 80 | Q_ASSERT(contentItem); 81 | QJsonObject obj{ { "x", m_view.position().x() }, 82 | { "y", m_view.position().y() + titleItem->height() }, 83 | { "w", contentItem->width() }, 84 | { "h", contentItem->height() } }; 85 | args << QString(QJsonDocument(obj).toJson()); 86 | } 87 | 88 | m_processMgr.createProcess(qApp->applicationDirPath() + "/Sub.exe", args); 89 | 90 | m_processMap[name] = 0; 91 | m_tabList.append(name); 92 | emit tabListChanged(); 93 | } 94 | setCurrentTab(name); 95 | } 96 | 97 | void TabMgr::closeTab(const QString &name) 98 | { 99 | if (name == s_mainStr) 100 | { 101 | return; 102 | } 103 | if (name == currentTab()) 104 | { 105 | setCurrentTab(s_mainStr); 106 | } 107 | m_tabList.removeOne(name); 108 | emit tabListChanged(); 109 | m_processMap.erase(name); 110 | QJsonObject obj{ { "operator", "quit" } }; 111 | m_ipc.sendData(name, QJsonDocument(obj).toJson()); 112 | } 113 | 114 | const QStringList &TabMgr::tabList() const 115 | { 116 | return m_tabList; 117 | } 118 | 119 | const QString &TabMgr::currentTab() const 120 | { 121 | return m_currentTab; 122 | } 123 | 124 | void TabMgr::showMaximized() 125 | { 126 | m_view.showMaximized(); 127 | } 128 | 129 | void TabMgr::showMinimized() 130 | { 131 | m_view.showMinimized(); 132 | } 133 | 134 | void TabMgr::showNormal() 135 | { 136 | m_view.showNormal(); 137 | } 138 | 139 | void TabMgr::setCurrentTab(const QString ¤tTab) 140 | { 141 | if (m_currentTab == currentTab) 142 | { 143 | return; 144 | } 145 | //新的页面不是main则raise,否则不处理 146 | if (currentTab != s_mainStr) 147 | { 148 | raiseSubProcess(currentTab); 149 | } 150 | //旧的页面不是main则lower,否则不处理 151 | if (m_currentTab != s_mainStr) 152 | { 153 | lowerSubProcess(m_currentTab); 154 | } 155 | m_currentTab = currentTab; 156 | emit currentTabChanged(); 157 | } 158 | 159 | void TabMgr::onReadyReay(const QString &socketName, const QByteArray &data) 160 | { 161 | QJsonParseError error; 162 | QJsonDocument doc = QJsonDocument::fromJson(data, &error); 163 | if (doc.isNull()) 164 | { 165 | qWarning() << "parseError " << error.errorString(); 166 | return; 167 | } 168 | QJsonObject obj = doc.object(); 169 | for (auto key : obj.keys()) 170 | { 171 | if (key == QStringLiteral("winid")) 172 | { 173 | quint64 winid = obj[key].toString().toULongLong(); 174 | qWarning() << "received winid" << winid; 175 | m_processMap[socketName] = winid; 176 | auto hw = ::SetParent(reinterpret_cast(winid), reinterpret_cast(m_view.winId())); 177 | if (nullptr == hw) 178 | { 179 | std::cout << GetLastErrorAsString() << std::endl; 180 | continue; 181 | } 182 | QQuickItem *contentItem = m_view.rootObject()->findChild("contentRect"); 183 | Q_ASSERT(contentItem); 184 | QQuickItem *titleItem = m_view.rootObject()->findChild("titleRect"); 185 | Q_ASSERT(titleItem); 186 | ::MoveWindow(reinterpret_cast(winid), 187 | 0, 188 | static_cast(titleItem->height()), 189 | static_cast(contentItem->width()), 190 | static_cast(contentItem->height()), 191 | true); 192 | } 193 | } 194 | } 195 | 196 | void TabMgr::syncSize() 197 | { 198 | QQuickItem *contentItem = m_view.rootObject()->findChild("contentRect"); 199 | Q_ASSERT(contentItem); 200 | QQuickItem *titleItem = m_view.rootObject()->findChild("titleRect"); 201 | Q_ASSERT(titleItem); 202 | QJsonObject obj{ 203 | { "operator", "resize" }, 204 | { "x", 0}, 205 | { "y", titleItem->height()}, 206 | { "w", contentItem->width()}, 207 | { "h", contentItem->height()}, 208 | }; 209 | QByteArray json = QJsonDocument(obj).toJson(); 210 | for (auto tab : m_tabList) { 211 | m_ipc.sendData(tab, json); 212 | } 213 | } 214 | 215 | void TabMgr::raiseSubProcess(const QString &subProcessName) 216 | { 217 | // auto itor = m_processMap.find(subProcessName); 218 | // if (itor != m_processMap.end()) 219 | // { 220 | // HWND hwnd = (HWND)(itor->second); 221 | // ::ShowWindow(hwnd, SW_SHOW); 222 | 223 | // QQuickItem *contentItem = m_view.rootObject()->findChild("contentRect"); 224 | // Q_ASSERT(contentItem); 225 | // QQuickItem *titleItem = m_view.rootObject()->findChild("titleRect"); 226 | // Q_ASSERT(titleItem); 227 | // ::MoveWindow(hwnd, 0, titleItem->height(), contentItem->width(), contentItem->height(), true); 228 | 229 | // qWarning() << "raise" << itor->second; 230 | // } 231 | QJsonObject obj{ { "operator", "show" } }; 232 | m_ipc.sendData(subProcessName, QJsonDocument(obj).toJson()); 233 | } 234 | 235 | void TabMgr::lowerSubProcess(const QString &subProcessName) 236 | { 237 | // auto itor = m_processMap.find(subProcessName); 238 | // if (itor != m_processMap.end()) 239 | // { 240 | // HWND hwnd = (HWND)(itor->second); 241 | 242 | // QQuickItem *contentItem = m_view.rootObject()->findChild("contentRect"); 243 | // Q_ASSERT(contentItem); 244 | 245 | // ::MoveWindow(hwnd, 2000, 2000, contentItem->width(), contentItem->height(), true); 246 | 247 | // ::ShowWindow(hwnd, SW_HIDE); 248 | // qWarning() << "lower" << itor->second; 249 | // } 250 | QJsonObject obj{ { "operator", "hide" } }; 251 | m_ipc.sendData(subProcessName, QJsonDocument(obj).toJson()); 252 | } 253 | -------------------------------------------------------------------------------- /Main/Src/TabMgr.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "Common.h" 9 | #include "IPC.h" 10 | #include "ProcessMgr.h" 11 | 12 | const static QString s_mainStr = QStringLiteral("main"); 13 | class TabMgr : public QObject 14 | { 15 | Q_OBJECT 16 | Q_PROPERTY(QStringList tabList READ tabList NOTIFY tabListChanged) 17 | Q_PROPERTY(QString currentTab READ currentTab NOTIFY currentTabChanged) 18 | public: 19 | explicit TabMgr(QObject *parent = nullptr); 20 | ~TabMgr(); 21 | Q_INVOKABLE void activeTab(const QString &name); 22 | Q_INVOKABLE void closeTab(const QString &name); 23 | 24 | const QStringList &tabList() const; 25 | 26 | const QString ¤tTab() const; 27 | 28 | Q_INVOKABLE void showMaximized(); 29 | Q_INVOKABLE void showMinimized(); 30 | Q_INVOKABLE void showNormal(); 31 | 32 | signals: 33 | 34 | void tabListChanged(); 35 | 36 | void currentTabChanged(); 37 | 38 | public slots: 39 | 40 | void setCurrentTab(const QString ¤tTab); 41 | private slots: 42 | 43 | void onReadyReay(const QString &socketName, const QByteArray &data); 44 | void syncSize(); 45 | private: 46 | void raiseSubProcess(const QString &subProcessName); 47 | void lowerSubProcess(const QString &subProcessName); 48 | private: 49 | 50 | QStringList m_tabList; 51 | QString m_currentTab = s_mainStr; 52 | QQuickView m_view; 53 | ProcessMgr m_processMgr; 54 | IPC m_ipc; 55 | 56 | // 57 | std::unordered_map m_processMap; 58 | }; 59 | 60 | -------------------------------------------------------------------------------- /Main/Src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "TabMgr.h" 3 | 4 | int main(int argc, char **argv) 5 | { 6 | qSetMessagePattern("[%{time yyyyMMdd h:mm:ss.zzz t} %{if-debug}D%{endif}%{if-info}I%{endif}%{if-warning}W%{endif}%{if-critical}C%{endif}%{if-fatal}F%{endif}] %{file}:%{line} - %{message}"); 7 | QApplication app(argc, argv); 8 | TabMgr tabMgr; 9 | (void)tabMgr; 10 | app.exec(); 11 | } 12 | -------------------------------------------------------------------------------- /MulitProcessTab.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | SUBDIRS += \ 3 | Main \ 4 | Sub 5 | CONFIG += ordered 6 | OTHER_FILES += README.md 7 | CONFIG += c++11 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 多进程Demo 2 | 3 | 类似于Chrome浏览器,多进程按照Tab的方式显示 4 | 5 | (目前仅在windows平台使用) 6 | ## Build status 7 | | [Windows][win-link] | 8 | | :-----------------: | 9 | | ![win-badge] | 10 | 11 | [win-badge]: https://ci.appveyor.com/api/projects/status/9i0y893u0d9ayud9?svg=true "AppVeyor build status" 12 | [win-link]: https://ci.appveyor.com/project/jaredtao/mulitprocesstab "AppVeyor build status" 13 | 14 | ## License 15 | [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/jaredtao/MulitProcessTab/blob/master/LICENSE) 16 | 17 | ## 预览图 18 | 19 | 20 | 主页 21 | 22 | ![](DemoImages/main.png) 23 | 24 | Tab页 25 | 26 | ![](DemoImages/sub.png) 27 | 28 | ## 说明 29 | 30 | 标题栏和首页,是主进程,每个tab页面,是一个独立的子进程。 31 | 32 | 子进程窗口通过Windows API SetParent的方式,附加到主进程。 33 | 34 | Tab切换,使用进程间通讯机制,将目标Tab的窗口置顶。 35 | 36 | ## 进程间通讯机制 37 | 38 | 使用Qt的LocalSocket功能。 39 | 40 | 主进程启动之后,创建一个LoaclServer,使用随机uuid作为server名称。 41 | 42 | 创建Tab时启动子进程,将server名称传递给子进程,子进程启动后,创建LoclaSocket来连接server。 43 | 44 | ## 开发环境 45 | 46 | * Qt 5.9.x Windows 47 | ### 联系方式: 48 | 49 | *** 50 | 51 | | 作者 | 涛哥 | 52 | | ---- | -------------------------------- | 53 | | QQ、TIM | 759378563 | 54 | | 微信 | xsd2410421 | 55 | | 邮箱 | jared2020@163.com | 56 | | blog | https://jaredtao.github.io/ | 57 | 58 | *** 59 | 60 | QQ(TIM)、微信二维码 61 | 62 | 63 | 64 | 65 | ###### 请放心联系我,乐于提供咨询服务,也可洽谈有偿技术支持相关事宜。 66 | 67 | *** 68 | #### **打赏** 69 | 70 | 71 | ###### 觉得分享的内容还不错, 就请作者喝杯奶茶吧~~ 72 | *** 73 | 74 | -------------------------------------------------------------------------------- /Sub/Qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | Qml/Main.qml 4 | 5 | 6 | -------------------------------------------------------------------------------- /Sub/Qml/Main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Rectangle { 4 | color: Qt.rgba(Math.random(),Math.random(),Math.random(),Math.random()) 5 | 6 | Rectangle{ 7 | width: 100 8 | height: 100 9 | anchors.bottom: parent.bottom 10 | anchors.right: parent.right 11 | color: Qt.rgba(Math.random(),Math.random(),Math.random(),Math.random()) 12 | } 13 | Rectangle { 14 | id: center 15 | width: 100 16 | height: width 17 | color: Qt.rgba(Math.random(),Math.random(),Math.random(),Math.random()) 18 | anchors.centerIn: parent 19 | RotationAnimation { 20 | from: 0 21 | to: 360 22 | duration: 1000 23 | target: center 24 | loops: Animation.Infinite 25 | running: true 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sub/Src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | int main(int argc, char *argv[]) 10 | { 11 | if (argc != 4) 12 | { 13 | std::cout << "Usage: " << argv[0] << " serverSocketName processName geometry" << std::endl; 14 | return -1; 15 | } 16 | QString serverSocketName = argv[1]; 17 | QString processName = argv[2]; 18 | QString geometryStr = argv[3]; 19 | QJsonDocument geometrydoc = QJsonDocument::fromJson(geometryStr.toUtf8()); 20 | QJsonObject geometryObj = geometrydoc.object(); 21 | int x = geometryObj["x"].toInt(); 22 | int y = geometryObj["y"].toInt(); 23 | int w = geometryObj["w"].toInt(); 24 | int h = geometryObj["h"].toInt(); 25 | 26 | std::cout << argv[1] << "started" << std::endl; 27 | QApplication app(argc, argv); 28 | QQuickView view; 29 | view.setGeometry(x, y, w, h); 30 | QLocalSocket socket; 31 | socket.connectToServer(serverSocketName); 32 | if (!socket.waitForConnected()) 33 | { 34 | std::cout << "Connect Failed" << std::endl; 35 | return -2; 36 | } 37 | 38 | //启动时发送 {"processName": "$processName"}, 自报名称给服务器。服务器用来做索引。 39 | QJsonObject processNameObj{ 40 | { "processName", QString(processName) }, 41 | }; 42 | socket.write(QJsonDocument(processNameObj).toJson()); 43 | 44 | //发送句柄给服务器,之后view的坐标、宽高及隐藏显示 都由server进程控制 45 | QJsonObject winIdObj{ { "winid", QString::number(static_cast(view.winId())) } }; 46 | std::cout << "send winid" << static_cast(view.winId()) << std::endl; 47 | socket.write(QJsonDocument(winIdObj).toJson()); 48 | QObject::connect(&socket, &QLocalSocket::readyRead, [&]() { 49 | QJsonDocument doc = QJsonDocument::fromJson(socket.readAll()); 50 | QJsonObject obj = doc.object(); 51 | for (auto key : obj.keys()) 52 | { 53 | if (key == QStringLiteral("operator")) 54 | { 55 | if (obj[key].toString() == QStringLiteral("quit")) 56 | { 57 | std::cout << "quit" << std::endl; 58 | app.quit(); 59 | } 60 | else if (obj[key].toString() == QStringLiteral("show")) 61 | { 62 | std::cout << "show" << std::endl; 63 | view.show(); 64 | } 65 | else if (obj[key].toString() == QStringLiteral("hide")) 66 | { 67 | std::cout << "hide" << std::endl; 68 | view.hide(); 69 | } 70 | else if (obj[key].toString() == QStringLiteral("resize")) 71 | { 72 | int x = obj["x"].toInt(); 73 | int y = obj["y"].toInt(); 74 | int w = obj["w"].toInt(); 75 | int h = obj["h"].toInt(); 76 | view.setGeometry(x, y, w, h); 77 | std::cout << "resize " << x << " " << y << " " << w << " " << h << std::endl; 78 | } 79 | } 80 | } 81 | }); 82 | view.setResizeMode(QQuickView::SizeRootObjectToView); 83 | view.setFlags(Qt::FramelessWindowHint | Qt::ForeignWindow); 84 | view.setSource(QUrl("qrc:/Qml/Main.qml")); 85 | view.show(); 86 | app.exec(); 87 | } 88 | -------------------------------------------------------------------------------- /Sub/Sub.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | TARGET= Sub 3 | include($$PWD/../common.pri) 4 | SOURCES += \ 5 | Src/main.cpp 6 | 7 | RESOURCES += \ 8 | Qml.qrc 9 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '{build}' 2 | 3 | branches: 4 | except: 5 | - project/travis 6 | environment: 7 | matrix: 8 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 9 | platform: x86 10 | qt: 5.9 11 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 12 | platform: x86 13 | qt: 5.10 14 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 15 | platform: x86 16 | qt: 5.11 17 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 18 | platform: x64 19 | qt: 5.9 20 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 21 | platform: x64 22 | qt: 5.10 23 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 24 | platform: x64 25 | qt: 5.11 26 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 27 | platform: x64 28 | qt: 5.9 29 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 30 | platform: x64 31 | qt: 5.10 32 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 33 | platform: x64 34 | qt: 5.11 35 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 36 | platform: x64 37 | qt: 5.12 38 | build_script: 39 | - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2015" set msvc=msvc2015 40 | - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2015" set vs=C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC 41 | - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" set msvc=msvc2017 42 | - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" set vs=C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build 43 | - if "%platform%"=="x86" set QTDIR=C:\Qt\%qt%\%msvc% 44 | - if "%platform%"=="x64" set QTDIR=C:\Qt\%qt%\%msvc%_64 45 | - set PATH=%PATH%;%QTDIR%\bin; 46 | - if "%platform%"=="x86" set vcvarsall=%vs%\vcvarsall.bat 47 | - if "%platform%"=="x64" set vcvarsall=%vs%\vcvarsall.bat 48 | - if "%platform%"=="x86" call "%vcvarsall%" x86 49 | - if "%platform%"=="x64" call "%vcvarsall%" x64 50 | - qmake 51 | - nmake 52 | 53 | -------------------------------------------------------------------------------- /common.pri: -------------------------------------------------------------------------------- 1 | CONFIG(debug, debug|release) { 2 | DESTDIR = $$absolute_path($$PWD/bin/Debug) 3 | } else { 4 | DESTDIR = $$absolute_path($$PWD/bin/Release) 5 | } 6 | QT += core gui qml quick widgets 7 | 8 | --------------------------------------------------------------------------------