واجهات برمجة Electron.js الشاملة

هذا الدليل يقدم شرحًا مفصلاً لواجهات برمجة التطبيق (API) الأساسية في Electron مع أمثلة عملية وشرح لكل طريقة وخاصية. نوضح أيضًا أفضل الممارسات والبدائل المتاحة لكل API.

واجهة App

واجهة app هي المسؤولة عن التحكم في دورة حياة تطبيق Electron. تتيح لك إدارة سلوك التطبيق بشكل عام وتلقي الأحداث المتعلقة بدورة حياته، مثل بدء التشغيل والإغلاق. يمكن استخدامها للوصول إلى معلومات التطبيق والمسارات الخاصة به.

const { app } = require('electron')

يمكن استيراد واجهة app فقط من العملية الرئيسية (Main Process). محاولة استخدامها مباشرة في عملية العرض (Renderer Process) ستؤدي إلى خطأ.

دورة حياة التطبيق

تطبيق Electron يمر بعدة مراحل أساسية خلال دورة حياته، وتوفر واجهة app أحداثًا خاصة بكل مرحلة يمكنك الاستجابة لها.

المراحل الرئيسية لدورة الحياة:

  1. التهيئة: تحميل التطبيق في الذاكرة وتهيئة المكونات الأساسية
  2. الاستعداد: عندما يكون Electron جاهزًا (حدث ready)
  3. نشاط التطبيق: تشغيل عادي، إنشاء نوافذ، استجابة للأحداث
  4. إغلاق النوافذ: عند إغلاق كل النوافذ (حدث window-all-closed)
  5. قبل الإغلاق: قبل إغلاق التطبيق (حدث before-quit)
  6. أثناء الإغلاق: أثناء إغلاق التطبيق (حدث will-quit)
  7. الإغلاق: بعد إغلاق التطبيق (حدث quit)
const { app } = require('electron')

// عند تهيئة التطبيق
console.log('بدء تشغيل التطبيق...')

// عند استعداد Electron
app.whenReady().then(() => {
  console.log('التطبيق جاهز الآن')
  createWindow() // دالة لإنشاء النافذة الرئيسية
})

// عند إغلاق جميع النوافذ
app.on('window-all-closed', () => {
  console.log('تم إغلاق جميع النوافذ')
  
  // في Windows و Linux، عادة ما يتم إغلاق التطبيق عند إغلاق كل النوافذ
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

// في macOS، عند النقر على أيقونة الـ Dock
app.on('activate', () => {
  console.log('تم تنشيط التطبيق')
  
  // إعادة إنشاء النافذة إذا لم تكن موجودة
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow()
  }
})

// قبل إغلاق التطبيق
app.on('before-quit', () => {
  console.log('التطبيق على وشك الإغلاق، حفظ البيانات...')
  // يمكنك هنا حفظ البيانات أو إجراء عمليات التنظيف
})

// أثناء إغلاق التطبيق
app.on('will-quit', () => {
  console.log('التطبيق يتم إغلاقه الآن...')
})

// بعد إغلاق التطبيق
app.on('quit', (event, exitCode) => {
  console.log(`تم إغلاق التطبيق بكود الخروج: ${exitCode}`)
})

في المثال أعلاه:

  • app.whenReady() يعيد Promise يتم حله عندما يكون Electron جاهزًا، وهو المكان المناسب لإنشاء النوافذ.
  • حدث window-all-closed يحدث عند إغلاق كل النوافذ، ويتم التعامل معه بشكل مختلف بين أنظمة التشغيل.
  • حدث activate خاص بنظام macOS عندما ينقر المستخدم على أيقونة التطبيق في شريط Dock.
  • أحداث before-quit و will-quit و quit تسمح بتنفيذ الإجراءات الضرورية قبل وأثناء وبعد الإغلاق.

ملاحظة: كل من هذه الأحداث له دور محدد في دورة حياة التطبيق، واختيار الحدث المناسب لتنفيذ الإجراءات مهم لضمان سلوك صحيح للتطبيق.

أحداث App

واجهة app تطلق عدة أحداث خلال دورة حياة التطبيق. فيما يلي الأحداث الرئيسية التي يمكنك الاستجابة لها:

أحداث دورة الحياة الأساسية
الحدث الوصف
will-finish-launching يتم إطلاقه بعد تهيئة التطبيق الأساسية وقبل حدث 'ready'
ready يتم إطلاقه عندما يكمل Electron التهيئة (استخدم app.whenReady() بدلاً من الاستماع لهذا الحدث مباشرة)
window-all-closed يتم إطلاقه عندما يتم إغلاق جميع النوافذ
before-quit يتم إطلاقه قبل أن يبدأ التطبيق في الإغلاق
will-quit يتم إطلاقه عندما تكون جميع النوافذ مغلقة والتطبيق سيتم إغلاقه
quit يتم إطلاقه عندما يتم إغلاق التطبيق
أحداث خاصة بنظام التشغيل
الحدث الوصف
activate يتم إطلاقه عندما يتم تنشيط التطبيق (macOS)
browser-window-blur يتم إطلاقه عندما تفقد نافذة التركيز
browser-window-focus يتم إطلاقه عندما تكتسب نافذة التركيز
browser-window-created يتم إطلاقه عندما يتم إنشاء نافذة جديدة
أحداث أخرى
الحدث الوصف
open-file يتم إطلاقه عندما يحاول المستخدم فتح ملف باستخدام التطبيق (macOS)
open-url يتم إطلاقه عندما يحاول المستخدم فتح رابط URL باستخدام التطبيق (macOS)
second-instance يتم إطلاقه عند محاولة تشغيل نسخة ثانية من التطبيق مع تفعيل خاصية استبعاد النسخ المتعددة
desktop-capturer-get-sources يتم إطلاقه عند استدعاء desktopCapturer.getSources()
// مثال لاستخدام أحداث متنوعة من app

// منع فتح نسخ متعددة من التطبيق
const gotTheLock = app.requestSingleInstanceLock()

if (!gotTheLock) {
  // إذا كانت هناك نسخة أخرى قيد التشغيل، قم بإغلاق هذه النسخة
  app.quit()
} else {
  // هذه هي النسخة الرئيسية من التطبيق
  
  // عند محاولة تشغيل نسخة ثانية
  app.on('second-instance', (event, commandLine, workingDirectory) => {
    // إذا كان المستخدم يحاول فتح نسخة أخرى، ركز على النافذة الموجودة
    const mainWindow = BrowserWindow.getAllWindows()[0]
    if (mainWindow) {
      if (mainWindow.isMinimized()) mainWindow.restore()
      mainWindow.focus()
    }
  })
  
  // عند بدء الإغلاق
  let isQuitting = false
  
  app.on('before-quit', () => {
    isQuitting = true
    // يمكنك هنا حفظ حالة التطبيق، إغلاق الاتصالات، إلخ
  })
  
  // في macOS، عند فتح ملف بواسطة التطبيق
  app.on('open-file', (event, path) => {
    event.preventDefault()
    console.log(`تم محاولة فتح الملف: ${path}`)
    // افتح الملف في التطبيق
  })
  
  // عندما يتغير وضع الطاقة في الجهاز (مفيد للاستجابة لوضع البطارية المنخفضة)
  app.on('power-monitor-on-battery', () => {
    console.log('تم الانتقال لوضع البطارية، تقليل استهلاك الطاقة...')
    // قم بتقليل استهلاك الطاقة (مثلاً: تقليل معدل تحديث البيانات)
  })
  
  app.on('power-monitor-on-ac', () => {
    console.log('تم الانتقال لوضع الطاقة المتصلة، استعادة الأداء الكامل...')
    // استعادة الأداء الكامل
  })
}

ملاحظة مهمة: يمكنك استخدام app.isPackaged للتمييز بين وضع التطوير ووضع الإنتاج، وهو مفيد لتنفيذ سلوكيات مختلفة بناءً على بيئة التشغيل.

طرق App

توفر واجهة app مجموعة واسعة من الطرق (Methods) للتحكم في سلوك التطبيق. فيما يلي أهم هذه الطرق:

الطرق الأساسية
الطريقة الوصف
app.quit() محاولة إغلاق جميع النوافذ وإنهاء التطبيق
app.exit([exitCode]) إغلاق التطبيق فورًا بكود الخروج المحدد (افتراضيًا 0)
app.relaunch([options]) إعادة تشغيل التطبيق عندما تنتهي النسخة الحالية
app.focus([options]) التركيز على التطبيق (جعله نشطًا) في Windows و Linux
app.getAppPath() الحصول على مسار دليل التطبيق الحالي
app.getPath(name) الحصول على مسار دليل خاص (مثل 'home', 'appData', 'temp', 'userData', إلخ)
طرق النوافذ الحديثة
الطريقة الوصف
app.setUserTasks(tasks) إضافة مهام محددة إلى قائمة Jump List في Windows
app.requestSingleInstanceLock() منع تشغيل نسخ متعددة من التطبيق
app.hasSingleInstanceLock() التحقق مما إذا كان قفل النسخة الوحيدة ساري المفعول
app.releaseSingleInstanceLock() تحرير قفل النسخة الوحيدة
طرق إضافية
الطريقة الوصف
app.setName(name) تعيين اسم التطبيق
app.getName() الحصول على اسم التطبيق الحالي
app.setLoginItemSettings(settings) تعيين خصائص تشغيل التطبيق عند بدء النظام
app.getLoginItemSettings([options]) الحصول على إعدادات تشغيل التطبيق عند بدء النظام
app.setAboutPanelOptions(options) تعيين خيارات لوحة "حول التطبيق" (macOS)
// أمثلة على استخدام طرق app

// بدء التطبيق عند بدء تشغيل النظام
app.setLoginItemSettings({
  openAtLogin: true,
  openAsHidden: false, // فتح النافذة عند بدء التشغيل
  path: process.execPath, // مسار ملف التنفيذ
  args: ['--autostart'] // وسائط إضافية
})

// الحصول على مسارات مهمة
const userDataPath = app.getPath('userData')
console.log(`مسار بيانات المستخدم: ${userDataPath}`)

const downloadsPath = app.getPath('downloads')
console.log(`مسار التنزيلات: ${downloadsPath}`)

// الحصول على معلومات عن التطبيق
console.log(`اسم التطبيق: ${app.getName()}`)
console.log(`إصدار التطبيق: ${app.getVersion()}`)
console.log(`مسار التطبيق: ${app.getAppPath()}`)
console.log(`هل التطبيق معبأ؟: ${app.isPackaged}`)

// إعادة تشغيل التطبيق (مفيد بعد التحديث)
app.relaunch({ args: process.argv.slice(1).concat(['--relaunch']) })
app.exit(0)

// ضبط معلومات "حول" للتطبيق (macOS)
app.setAboutPanelOptions({
  applicationName: 'تطبيقي الرائع',
  applicationVersion: app.getVersion(),
  copyright: 'حقوق النشر © ' + new Date().getFullYear(),
  credits: 'فريق التطوير',
  authors: ['المطور الأول', 'المطور الثاني'],
  website: 'https://example.com',
  iconPath: path.join(__dirname, 'assets/icon.png')
})

طرق بديلة لمهام شائعة:

  • بدلاً من app.getPath('userData'): يمكنك استخدام مكتبة electron-store للتعامل مع تخزين البيانات بشكل أبسط.
  • بدلاً من app.setLoginItemSettings(): يمكنك استخدام node-auto-launch للتحكم في بدء التشغيل التلقائي عبر جميع الأنظمة.
  • بدلاً من app.relaunch() و app.exit(): يمكنك استخدام electron-updater للتحكم في تحديث وإعادة تشغيل التطبيق.

خصائص App

توفر واجهة app عدة خصائص يمكن قراءتها للحصول على معلومات حول التطبيق والبيئة:

الخاصية الوصف
app.isPackaged قيمة منطقية تشير إلى ما إذا كان التطبيق معبأ (تشغيل من حزمة وليس في وضع التطوير)
app.name اسم التطبيق، مأخوذ من name في package.json
app.userAgentFallback سلسلة User Agent الافتراضية المستخدمة في التطبيق
app.allowRendererProcessReuse عندما تكون true، يتم إعادة استخدام عمليات العرض للصفحات بنفس url
app.isReady() طريقة ترجع قيمة منطقية تشير إلى ما إذا كان Electron قد أكمل التهيئة الأولية
// استخدام خصائص app

console.log(`اسم التطبيق: ${app.name}`)
console.log(`هل التطبيق معبأ: ${app.isPackaged}`)
console.log(`هل Electron جاهز: ${app.isReady()}`)

// التمييز بين وضع التطوير ووضع الإنتاج
if (app.isPackaged) {
  // كود خاص بوضع الإنتاج
  console.log('تشغيل في وضع الإنتاج')
} else {
  // كود خاص بوضع التطوير
  console.log('تشغيل في وضع التطوير')
  // تفعيل أدوات التطوير، التحميل الفوري، إلخ
}

// تعديل سلسلة User Agent
app.userAgentFallback = `${app.name}/${app.getVersion()} (${process.platform}; ${process.arch})`

واجهة BrowserWindow

تُستخدم واجهة BrowserWindow لإنشاء وإدارة نوافذ سطح المكتب في تطبيق Electron. كل نافذة يمكنها تحميل صفحة ويب (HTML) وتعرضها في عملية عرض منفصلة.

const { BrowserWindow } = require('electron')

يمكن استيراد BrowserWindow فقط في العملية الرئيسية (Main Process).

إنشاء النوافذ

لإنشاء نافذة جديدة، قم بإنشاء نسخة جديدة من BrowserWindow مع تحديد الخيارات المطلوبة.

const { BrowserWindow, app } = require('electron')
const path = require('path')

// انتظر حتى يكون Electron جاهزًا
app.whenReady().then(() => {
  // إنشاء نافذة جديدة
  const mainWindow = new BrowserWindow({
    width: 800,               // عرض النافذة بالبكسل
    height: 600,              // ارتفاع النافذة بالبكسل
    title: 'تطبيق Electron',  // عنوان النافذة
    
    // الخيارات الأساسية للنافذة
    show: false,              // لا تظهر النافذة عند الإنشاء
    center: true,             // توسيط النافذة على الشاشة
    resizable: true,          // السماح بتغيير حجم النافذة
    movable: true,            // السماح بتحريك النافذة
    minimizable: true,        // السماح بتصغير النافذة
    maximizable: true,        // السماح بتكبير النافذة
    closable: true,           // السماح بإغلاق النافذة
    
    // الخصائص المتقدمة للنافذة
    backgroundColor: '#FFFFFF',  // لون خلفية النافذة
    icon: path.join(__dirname, 'assets/icon.png'),  // أيقونة النافذة
    minWidth: 400,            // الحد الأدنى للعرض
    minHeight: 300,           // الحد الأدنى للارتفاع
    maxWidth: 1200,           // الحد الأقصى للعرض
    maxHeight: 900,           // الحد الأقصى للارتفاع
    
    // إعدادات webPreferences تحدد سلوك عملية العرض
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),  // سكريبت التحميل المسبق
      nodeIntegration: false,  // تعطيل تكامل Node.js لأسباب أمنية
      contextIsolation: true,  // تفعيل عزل السياق
      sandbox: true,           // تفعيل الصندوق الرملي
      spellcheck: true,        // تفعيل التدقيق الإملائي
      enableRemoteModule: false  // تعطيل الوحدة البعيدة
    }
  })
  
  // تحميل محتوى في النافذة (ملف HTML أو عنوان URL)
  mainWindow.loadFile('index.html')
  // أو: mainWindow.loadURL('https://example.com')
  
  // إظهار النافذة عند اكتمال التحميل لتجنب الوميض
  mainWindow.once('ready-to-show', () => {
    mainWindow.show()
  })
  
  // معالجة حدث الإغلاق
  mainWindow.on('closed', () => {
    // إزالة الإشارة إلى الكائن عند إغلاق النافذة
    mainWindow = null
  })
})

شرح خيارات BrowserWindow:

  • الأبعاد والموقع: width, height, center, x, y تحدد حجم وموقع النافذة.
  • المظهر: title, icon, backgroundColor تحدد كيف تبدو النافذة.
  • السلوك: show, resizable, movable تتحكم في سلوك النافذة.
  • الأمان والأداء: webPreferences تحدد كيفية تشغيل المحتوى داخل النافذة.

أفضل الممارسات:

  • دائمًا استخدم contextIsolation: true و nodeIntegration: false لتحسين أمان التطبيق.
  • استخدم حدث ready-to-show لإظهار النافذة بعد اكتمال التحميل لتجنب شاشة بيضاء أو وميض.
  • إذا كنت تخزن إشارة إلى النافذة، قم بتعيينها إلى null عند الإغلاق لمساعدة جامع النفايات.
  • حدد minWidth و minHeight لمنع النوافذ الصغيرة جدًا التي قد تفسد تخطيط التطبيق.

أحداث BrowserWindow

كائن BrowserWindow يطلق مجموعة من الأحداث التي يمكنك الاستماع لها ومعالجتها:

أحداث حالة النافذة
الحدث الوصف
ready-to-show يتم إطلاقه عندما تكون الصفحة جاهزة للعرض
show يتم إطلاقه عندما تصبح النافذة مرئية
hide يتم إطلاقه عندما يتم إخفاء النافذة
focus يتم إطلاقه عندما تكتسب النافذة التركيز
blur يتم إطلاقه عندما تفقد النافذة التركيز
أحداث تغيير النافذة
الحدث الوصف
resize يتم إطلاقه عندما يتم تغيير حجم النافذة
move يتم إطلاقه عندما يتم نقل النافذة
maximize يتم إطلاقه عندما يتم تكبير النافذة
unmaximize يتم إطلاقه عندما تستعيد النافذة حجمها من التكبير
minimize يتم إطلاقه عندما يتم تصغير النافذة
restore يتم إطلاقه عندما تستعيد النافذة حجمها من التصغير
أحداث دورة الحياة
الحدث الوصف
close يتم إطلاقه عندما يحاول المستخدم إغلاق النافذة (يمكن إلغاؤه)
closed يتم إطلاقه بعد إغلاق النافذة فعليًا
page-title-updated يتم إطلاقه عندما يتغير عنوان الصفحة
// أمثلة على معالجة أحداث BrowserWindow

// منع إغلاق النافذة مباشرة (مثلاً لعرض تأكيد قبل الإغلاق)
mainWindow.on('close', (event) => {
  // إذا كانت هناك تغييرات غير محفوظة
  if (hasUnsavedChanges) {
    event.preventDefault() // منع الإغلاق التلقائي
    
    const { dialog } = require('electron')
    const choice = dialog.showMessageBoxSync(mainWindow, {
      type: 'question',
      buttons: ['حفظ وإغلاق', 'إغلاق بدون حفظ', 'إلغاء'],
      title: 'تأكيد الإغلاق',
      message: 'لديك تغييرات غير محفوظة. هل تريد حفظها قبل الإغلاق؟'
    })
    
    if (choice === 0) {
      // حفظ التغييرات ثم إغلاق
      saveChanges()
      mainWindow.destroy()
    } else if (choice === 1) {
      // إغلاق بدون حفظ
      mainWindow.destroy()
    }
    // إذا اختار المستخدم "إلغاء"، لا نفعل شيئًا والنافذة تبقى مفتوحة
  }
})

// معالجة تغيير حجم النافذة
mainWindow.on('resize', () => {
  const [width, height] = mainWindow.getSize()
  console.log(`تم تغيير حجم النافذة إلى: ${width}×${height}`)
  
  // يمكنك هنا حفظ الحجم الجديد للاستخدام في المرة القادمة
  saveWindowDimensions(width, height)
})

// معالجة تغيير حالة التركيز
mainWindow.on('focus', () => {
  console.log('النافذة نشطة الآن')
  // يمكنك هنا تحديث الواجهة أو إجراء عمليات عند استعادة التركيز
})

mainWindow.on('blur', () => {
  console.log('النافذة غير نشطة الآن')
  // يمكنك هنا تقليل استخدام الموارد أو إيقاف بعض العمليات مؤقتًا
})

// تنفيذ إجراء عندما تكون النافذة جاهزة للعرض
mainWindow.once('ready-to-show', () => {
  // show() تُستدعى هنا بدلاً من استدعائها فورًا بعد إنشاء النافذة
  mainWindow.show()
  
  // يمكنك أيضًا تنفيذ إجراءات أخرى، مثل:
  mainWindow.focus() // جعل النافذة نشطة
  // mainWindow.maximize() // تكبير النافذة
  
  // إظهار رسالة ترحيبية
  mainWindow.webContents.send('app-ready', { message: 'تم تحميل التطبيق بنجاح!' })
})

طرق BrowserWindow

يوفر BrowserWindow مجموعة غنية من الطرق للتحكم في النافذة بعد إنشائها:

طرق التحميل
الطريقة الوصف
win.loadFile(path) تحميل ملف HTML محلي في النافذة
win.loadURL(url[, options]) تحميل URL في النافذة، يمكن أن يكون عنوان ويب خارجي أو بروتوكول مخصص
win.reload() إعادة تحميل الصفحة الحالية
win.loadURL('file://' + path) طريقة بديلة لتحميل ملف محلي باستخدام بروتوكول file://
طرق التحكم في النافذة
الطريقة الوصف
win.show() إظهار النافذة وجعلها تركز
win.hide() إخفاء النافذة
win.close() محاولة إغلاق النافذة
win.focus() جعل النافذة تركز
win.blur() إزالة التركيز من النافذة
win.maximize() تكبير النافذة
win.minimize() تصغير النافذة
win.restore() استعادة النافذة من حالة التكبير أو التصغير
win.setFullScreen(flag) تعيين ما إذا كانت النافذة في وضع ملء الشاشة
طرق الإعدادات والخصائص
الطريقة الوصف
win.setTitle(title) تغيير عنوان النافذة
win.getTitle() الحصول على عنوان النافذة الحالي
win.setSize(width, height) تغيير حجم النافذة
win.getSize() الحصول على حجم النافذة الحالي كمصفوفة [width, height]
win.setPosition(x, y) تغيير موقع النافذة
win.getPosition() الحصول على موقع النافذة الحالي كمصفوفة [x, y]
win.setContentSize(width, height) تغيير حجم منطقة المحتوى (بدون إطار النافذة)
win.setResizable(resizable) تعيين ما إذا كان يمكن تغيير حجم النافذة
win.setAlwaysOnTop(flag) تعيين ما إذا كانت النافذة دائمًا في المقدمة
win.isVisible() التحقق مما إذا كانت النافذة مرئية
win.isMinimized() التحقق مما إذا كانت النافذة مصغرة
// أمثلة على استخدام طرق BrowserWindow

// التحكم في تحميل المحتوى
mainWindow.loadFile('index.html')
mainWindow.loadURL('https://example.com')

// إعادة تحميل الصفحة الحالية (مفيد أثناء التطوير)
mainWindow.reload()

// التحكم في مظهر النافذة وحالتها
mainWindow.maximize() // تكبير النافذة
mainWindow.setFullScreen(true) // تفعيل وضع ملء الشاشة
mainWindow.setAlwaysOnTop(true) // جعل النافذة دائمًا في المقدمة

// ضبط خصائص النافذة
mainWindow.setTitle('عنوان جديد للنافذة')
mainWindow.setSize(1024, 768)
mainWindow.setPosition(100, 100)
mainWindow.setContentSize(800, 600) // ضبط حجم منطقة المحتوى فقط
mainWindow.setResizable(false) // منع تغيير حجم النافذة

// الحصول على معلومات عن النافذة
const title = mainWindow.getTitle()
const [width, height] = mainWindow.getSize()
const [x, y] = mainWindow.getPosition()
const isVisible = mainWindow.isVisible()
const isFullScreen = mainWindow.isFullScreen()

console.log(`عنوان النافذة: ${title}`)
console.log(`أبعاد النافذة: ${width}×${height}`)
console.log(`موقع النافذة: (${x}, ${y})`)
console.log(`هل النافذة مرئية؟ ${isVisible}`)
console.log(`هل النافذة في وضع ملء الشاشة؟ ${isFullScreen}`)

// التحكم في سلوك النافذة
mainWindow.center() // توسيط النافذة على الشاشة
mainWindow.setMenuBarVisibility(false) // إخفاء شريط القوائم
mainWindow.setAutoHideMenuBar(true) // إخفاء تلقائي لشريط القوائم

// التعامل مع webContents (محتويات الويب) للنافذة
mainWindow.webContents.openDevTools() // فتح أدوات المطور
mainWindow.webContents.send('message-to-renderer', { data: 'بيانات للإرسال' }) // إرسال رسالة إلى عملية العرض

بدائل وأنماط مفيدة:

  1. حفظ واستعادة حالة النافذة:

    بدلاً من ضبط حجم وموقع النافذة يدويًا في كل مرة، يمكنك استخدام electron-window-state لحفظ واستعادة آخر حالة للنافذة تلقائيًا.

    const windowStateKeeper = require('electron-window-state')
    
    // استعادة حالة النافذة السابقة
    let mainWindowState = windowStateKeeper({
      defaultWidth: 800,
      defaultHeight: 600
    })
    
    // إنشاء النافذة باستخدام الحالة المحفوظة
    const mainWindow = new BrowserWindow({
      x: mainWindowState.x,
      y: mainWindowState.y,
      width: mainWindowState.width,
      height: mainWindowState.height,
      // ...الخيارات الأخرى
    })
    
    // تسجيل الحالة الحالية للنافذة عند إغلاقها
    mainWindowState.manage(mainWindow)
  2. واجهات متعددة:

    يمكنك إنشاء تطبيقات بواجهات متعددة باستخدام BrowserView بدلاً من فتح عدة نوافذ منفصلة.

خصائص BrowserWindow

بعد إنشاء نافذة BrowserWindow، يمكنك الوصول إلى بعض الخصائص المهمة:

الخاصية الوصف
win.webContents كائن WebContents المسؤول عن عرض وتحكم صفحة الويب داخل هذه النافذة
win.id رقم تعريف فريد للنافذة
BrowserWindow.getAllWindows() طريقة ساكنة ترجع مصفوفة تحتوي على جميع نوافذ BrowserWindow المفتوحة
BrowserWindow.getFocusedWindow() طريقة ساكنة ترجع النافذة التي تركز حاليًا، أو null إذا لم تكن هناك نافذة تركز
BrowserWindow.fromWebContents(webContents) طريقة ساكنة تجد النافذة التي تحتوي على كائن webContents المحدد
BrowserWindow.fromId(id) طريقة ساكنة تجد النافذة بواسطة معرفها الفريد
// أمثلة على استخدام خصائص BrowserWindow

// الوصول إلى webContents للنافذة
console.log(`هل النافذة تحمل صفحة؟ ${mainWindow.webContents.isLoading() ? 'نعم' : 'لا'}`)
console.log(`عنوان URL الحالي: ${mainWindow.webContents.getURL()}`)

// إرسال رسالة إلى عملية العرض
mainWindow.webContents.send('update-counter', { count: 5 })

// تنفيذ JavaScript في سياق الصفحة
mainWindow.webContents.executeJavaScript('document.title = "عنوان جديد"')
mainWindow.webContents.executeJavaScript('console.log("مرحبًا من العملية الرئيسية")')

// العمل مع جميع النوافذ
const allWindows = BrowserWindow.getAllWindows()
console.log(`عدد النوافذ المفتوحة: ${allWindows.length}`)

// البحث عن النافذة النشطة
const focusedWindow = BrowserWindow.getFocusedWindow()
if (focusedWindow) {
  console.log(`النافذة النشطة حاليًا هي: ${focusedWindow.getTitle()}`)
} else {
  console.log('لا توجد نافذة نشطة حاليًا')
}

// البحث عن نافذة بواسطة معرفها
const windowById = BrowserWindow.fromId(mainWindow.id)
if (windowById) {
  console.log(`تم العثور على النافذة: ${windowById.getTitle()}`)
}

واجهة IPC (الاتصال بين العمليات)

نظام IPC (Inter-Process Communication) يوفر آلية للتواصل بين العملية الرئيسية (Main Process) وعمليات العرض (Renderer Processes) في تطبيق Electron. هذا التواصل ضروري لأن كل عملية تعمل في سياق منفصل مع إمكانيات مختلفة.

// في العملية الرئيسية const { ipcMain } = require('electron') // في عملية العرض const { ipcRenderer } = require('electron')

ipcMain

ipcMain هي وحدة تستخدم في العملية الرئيسية للاستماع إلى الرسائل التي ترسلها عمليات العرض والرد عليها.

// في العملية الرئيسية (main.js)
const { ipcMain } = require('electron')

// استخدام ipcMain.on للاستماع لرسائل أحادية الاتجاه
ipcMain.on('async-message', (event, arg) => {
  console.log('تم استلام رسالة:', arg)
  
  // الرد على المرسل (اختياري)
  event.reply('async-reply', 'تم استلام رسالتك!')
  // أو باستخدام sender مباشرة
  // event.sender.send('async-reply', 'تم استلام رسالتك!')
})

// استخدام ipcMain.handle للاستماع لطلبات نمط الوعد/promise
ipcMain.handle('invoke-handler', async (event, arg) => {
  console.log('تم استلام استدعاء:', arg)
  
  // يمكن إجراء عمليات غير متزامنة هنا
  await new Promise(resolve => setTimeout(resolve, 1000))
  
  // القيمة المعادة ستكون نتيجة الوعد في عملية العرض
  return {
    success: true,
    data: 'هذه هي النتيجة',
    received: arg
  }
})

// استخدام ipcMain.handleOnce للاستماع مرة واحدة فقط
ipcMain.handleOnce('one-time-request', async (event, arg) => {
  console.log('تم استلام طلب لمرة واحدة:', arg)
  return { message: 'تمت معالجة الطلب لمرة واحدة' }
})

// إلغاء تسجيل معالج
// ipcMain.removeHandler('invoke-handler')

شرح الطرق الرئيسية:

  • ipcMain.on(channel, listener): استماع للرسائل غير المتزامنة على قناة محددة.
    • يمكنك الرد باستخدام event.reply() أو event.sender.send()
    • لا تعيد قيمة تلقائيًا إلى المرسل
  • ipcMain.handle(channel, handler): استماع لاستدعاءات من عملية العرض على قناة محددة.
    • يتوقع دالة معالج تُرجع قيمة أو وعد
    • القيمة المعادة أو نتيجة الوعد ستكون نتيجة الاستدعاء في عملية العرض
  • ipcMain.handleOnce(channel, handler): مثل handle لكن يستمع مرة واحدة فقط.
  • ipcMain.removeHandler(channel): إزالة معالج تم تسجيله من قبل.

تحذير أمني: كن حذرًا مع البيانات المتلقاة من عمليات العرض، فهي يمكن أن تتعرض للتلاعب. دائمًا تحقق من صحة البيانات الواردة قبل اتخاذ إجراءات في العملية الرئيسية.

ipcRenderer

ipcRenderer هي وحدة تستخدم في عملية العرض لإرسال الرسائل إلى العملية الرئيسية واستقبال الردود.

ملاحظة مهمة: مع تفعيل عزل السياق (contextIsolation) لأسباب أمنية، يجب كشف ipcRenderer عبر سكريبت preload باستخدام contextBridge. لا ينبغي استيراد ipcRenderer مباشرة في كود عملية العرض.

// في سكريبت preload.js
const { contextBridge, ipcRenderer } = require('electron')

// كشف واجهة آمنة للاتصال بين العمليات لعملية العرض
contextBridge.exposeInMainWorld('electronAPI', {
  // إرسال رسالة أحادية الاتجاه إلى العملية الرئيسية
  sendMessage: (message) => ipcRenderer.send('async-message', message),
  
  // استدعاء وانتظار رد من العملية الرئيسية
  invokeAction: (data) => ipcRenderer.invoke('invoke-handler', data),
  
  // استماع للرسائل من العملية الرئيسية
  onUpdateMessage: (callback) => {
    ipcRenderer.on('async-reply', (_event, value) => callback(value))
    
    // إرجاع دالة لإلغاء الاستماع عند الحاجة
    return () => {
      ipcRenderer.removeAllListeners('async-reply')
    }
  }
})

// في ملف renderer.js (عملية العرض)
// استخدام الواجهة التي تم كشفها في preload.js

// إرسال رسالة للعملية الرئيسية
document.getElementById('send-button').addEventListener('click', () => {
  window.electronAPI.sendMessage('مرحبًا من عملية العرض!')
})

// استدعاء وانتظار النتيجة (Promise)
document.getElementById('invoke-button').addEventListener('click', async () => {
  try {
    const result = await window.electronAPI.invokeAction({ 
      action: 'getInfo', 
      params: { id: 123 }
    })
    
    console.log('تم استلام النتيجة:', result)
    document.getElementById('result').textContent = JSON.stringify(result)
  } catch (error) {
    console.error('خطأ في الاستدعاء:', error)
  }
})

// الاستماع للرسائل من العملية الرئيسية
const cleanup = window.electronAPI.onUpdateMessage((message) => {
  console.log('رسالة واردة من العملية الرئيسية:', message)
  document.getElementById('messages').textContent = message
})

// إلغاء الاستماع عند الحاجة (مثلاً عند إزالة المكون)
// cleanup()

شرح الطرق الرئيسية:

  • ipcRenderer.send(channel, ...args): إرسال رسالة أحادية الاتجاه إلى العملية الرئيسية.
    • لا ينتظر ردًا (غير متزامن)
    • يمكن إرسال بيانات متعددة كوسائط
  • ipcRenderer.invoke(channel, ...args): استدعاء معالج في العملية الرئيسية وانتظار نتيجة.
    • يعيد وعد (Promise) بالنتيجة
    • أفضل أسلوب للاتصال ثنائي الاتجاه
  • ipcRenderer.on(channel, listener): الاستماع للرسائل من العملية الرئيسية.
  • ipcRenderer.removeListener(channel, listener): إزالة مستمع محدد.
  • ipcRenderer.removeAllListeners(channel): إزالة جميع المستمعين لقناة محددة.

أنماط التواصل بين العمليات

نمط الرسائل أحادية الاتجاه

إرسال رسائل من عملية العرض إلى العملية الرئيسية أو العكس، بدون توقع رد مباشر.

// في عملية العرض
window.electronAPI.sendMessage('تحديث-الحالة', { status: 'نشط' })

// في العملية الرئيسية
ipcMain.on('تحديث-الحالة', (event, data) => {
  console.log('حالة المستخدم:', data.status)
  // يمكن اتخاذ إجراء بناءً على الرسالة لكن بدون رد
})

نمط الطلب/الاستجابة (باستخدام invoke)

استدعاء وظيفة في العملية الرئيسية وانتظار النتيجة، مثل نمط الخادم/العميل.

// في عملية العرض
async function getUserData() {
  return await window.electronAPI.invokeAction('جلب-بيانات-المستخدم', { id: 123 })
}

// في العملية الرئيسية
ipcMain.handle('جلب-بيانات-المستخدم', async (event, data) => {
  // مثلاً: جلب بيانات من قاعدة البيانات
  return { name: 'أحمد', email: '[email protected]', id: data.id }
})

نمط الإشعارات (النشر/الاشتراك)

إرسال إشعارات من العملية الرئيسية إلى جميع عمليات العرض أو العكس.

// في العملية الرئيسية
function broadcastMessage(message) {
  // إرسال إلى جميع النوافذ
  BrowserWindow.getAllWindows().forEach(window => {
    window.webContents.send('إشعار', message)
  })
}

// في عملية العرض (لكل نافذة)
window.electronAPI.onNotification(message => {
  showNotification(message)
})

نمط التدفق (Stream)

إرسال سلسلة من الأحداث على مدى فترة من الزمن، مثل تقدم التحميل.

// في العملية الرئيسية
function startDownload(url) {
  let progress = 0
  
  const interval = setInterval(() => {
    progress += 10
    
    // إرسال تحديث التقدم إلى واجهة المستخدم
    mainWindow.webContents.send('تحديث-التقدم', { progress })
    
    if (progress >= 100) {
      clearInterval(interval)
      mainWindow.webContents.send('اكتمال-التحميل', { url })
    }
  }, 500)
}

// في عملية العرض
window.electronAPI.onProgressUpdate(data => {
  updateProgressBar(data.progress)
})

window.electronAPI.onDownloadComplete(data => {
  showCompletionMessage(data.url)
})

أفضل الممارسات للتواصل بين العمليات:

  • الأمان أولاً: دائمًا استخدم contextIsolation: true لحماية عملية العرض من الوصول المباشر لـ Node.js.
  • تحقق من البيانات: التحقق دائمًا من صحة البيانات المستلمة من عملية العرض قبل تنفيذ أي إجراء.
  • تسمية القنوات: استخدم تسميات واضحة ومنظمة للقنوات مثل 'module:action' (مثل: 'database:get-user').
  • فضّل invoke: استخدم ipcRenderer.invoke و ipcMain.handle بدلاً من send/on لتبسيط التعامل مع الأخطاء والوعود.
  • تجنب نقل البيانات الكبيرة: لا ترسل مجموعات بيانات كبيرة عبر IPC لتجنب مشاكل الأداء. فكر في استخدام SharedArrayBuffer أو الملفات المؤقتة للبيانات الكبيرة.

واجهة Dialog

واجهة dialog تتيح لك عرض مربعات حوار أصلية في نظام التشغيل، مثل فتح الملفات، حفظ الملفات، التنبيهات، وغيرها. هذه المربعات تظهر كجزء من واجهة سطح المكتب الأصلية، وليس كعناصر HTML داخل النافذة.

const { dialog } = require('electron')

يمكن استيراد dialog فقط في العملية الرئيسية (Main Process) أو من عملية العرض (Renderer Process) عبر remote (لكن يُنصح بتجنب استخدام remote للأمان).

طرق Dialog الرئيسية

مربعات الحوار الأساسية
الطريقة الوصف
dialog.showOpenDialog([browserWindow, ]options) عرض مربع حوار لاختيار ملف أو مجلد للفتح
dialog.showSaveDialog([browserWindow, ]options) عرض مربع حوار لاختيار موقع لحفظ ملف
dialog.showMessageBox([browserWindow, ]options) عرض مربع حوار يحتوي على رسالة وأزرار اختيارية
dialog.showErrorBox(title, content) عرض مربع حوار للأخطاء مع عنوان ومحتوى
// أمثلة على استخدام dialog

// استخدام مربع حوار الرسالة
async function showConfirmation() {
  const { response } = await dialog.showMessageBox({
    type: 'question',
    buttons: ['نعم', 'لا', 'إلغاء'],
    defaultId: 0,
    cancelId: 2,
    title: 'تأكيد',
    message: 'هل أنت متأكد من حذف هذا الملف؟',
    detail: 'لا يمكن التراجع عن هذا الإجراء.',
    checkboxLabel: 'لا تسألني مرة أخرى',
    checkboxChecked: false
  })
  
  // response هو مؤشر الزر المختار (0 = 'نعم', 1 = 'لا', 2 = 'إلغاء')
  return response === 0
}

// استخدام مربع حوار فتح ملف
async function openFile() {
  const { canceled, filePaths } = await dialog.showOpenDialog({
    title: 'اختر ملفًا',
    defaultPath: app.getPath('documents'),
    properties: ['openFile', 'multiSelections'],
    filters: [
      { name: 'صور', extensions: ['jpg', 'png', 'gif'] },
      { name: 'مستندات', extensions: ['pdf', 'doc', 'docx'] },
      { name: 'كل الملفات', extensions: ['*'] }
    ]
  })
  
  if (!canceled && filePaths.length > 0) {
    // استخدام الملفات المختارة
    console.log('الملفات المختارة:', filePaths)
    return filePaths
  }
  
  return null
}

// استخدام مربع حوار حفظ ملف
async function saveFile(content) {
  const { canceled, filePath } = await dialog.showSaveDialog({
    title: 'حفظ الملف',
    defaultPath: app.getPath('documents') + '/مستند.txt',
    buttonLabel: 'حفظ',
    filters: [
      { name: 'نصوص', extensions: ['txt'] },
      { name: 'كل الملفات', extensions: ['*'] }
    ],
    properties: ['createDirectory', 'showOverwriteConfirmation']
  })
  
  if (!canceled && filePath) {
    // حفظ المحتوى إلى الملف
    const fs = require('fs')
    fs.writeFileSync(filePath, content)
    return filePath
  }
  
  return null
}

// عرض رسالة خطأ
function showError(errorMessage) {
  dialog.showErrorBox('حدث خطأ', errorMessage)
}

عند استخدام مربعات الحوار:

  • جميع هذه الطرق تدعم الإصدارات المتزامنة والغير متزامنة (باستخدام Promises).
  • يُفضل استخدام الإصدارات الغير متزامنة (أي مع await) لتجنب تجميد واجهة المستخدم.
  • الوسيط browserWindow اختياري ويحدد النافذة التي سيرتبط بها مربع الحوار.
  • خيار filters يسمح بتقييد أنواع الملفات المعروضة في مربعات حوار فتح/حفظ الملفات.

بدائل لمربعات الحوار الأصلية:

  • مربعات حوار HTML مخصصة: إذا كنت بحاجة إلى مزيد من التخصيص في المظهر، يمكنك إنشاء نوافذ BrowserWindow ثانوية لتعمل كمربعات حوار.
  • مكتبات واجهة المستخدم: استخدام مكتبات مثل sweetalert2 أو bootstrap-modal داخل عملية العرض لإنشاء مربعات حوار أكثر جمالًا.