add chatserver & client

This commit is contained in:
backuppc
2025-11-12 13:57:13 +09:00
parent e19b404580
commit d977c9da83
26 changed files with 2662 additions and 118 deletions

View File

@@ -0,0 +1,283 @@
using System;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using Newtonsoft.Json;
namespace Project
{
/// <summary>
/// Background chat client service with auto-reconnect
/// </summary>
public class ChatClientService : IDisposable
{
private TcpClient tcpClient;
private NetworkStream stream;
private Thread receiveThread;
private Thread reconnectThread;
private bool isRunning;
private bool isConnected;
private string serverIp;
private int serverPort;
private string nickName;
private string employeeId;
private string hostName;
private string userGroup;
/// <summary>Connection status changed event</summary>
public event EventHandler<bool> ConnectionStatusChanged;
/// <summary>Message received event</summary>
public event EventHandler<ChatMessage> MessageReceived;
/// <summary>Connection status</summary>
public bool IsConnected => isConnected;
/// <summary>User nickname</summary>
public string NickName => nickName;
/// <summary>User employee ID</summary>
public string EmployeeId => employeeId;
/// <summary>User group</summary>
public string UserGroup => userGroup;
public ChatClientService(string serverIp, int serverPort)
{
this.serverIp = serverIp;
this.serverPort = serverPort;
this.hostName = System.Net.Dns.GetHostName();
}
/// <summary>
/// Start background service
/// </summary>
public void Start(string nickName, string employeeId, string userGroup)
{
if (isRunning)
return;
this.nickName = nickName;
this.employeeId = employeeId;
this.userGroup = userGroup;
isRunning = true;
// Start reconnect thread
reconnectThread = new Thread(ReconnectLoop)
{
IsBackground = true,
Name = "ChatReconnectThread"
};
reconnectThread.Start();
}
/// <summary>
/// Stop background service
/// </summary>
public void Stop()
{
isRunning = false;
Disconnect();
try
{
reconnectThread?.Join(2000);
receiveThread?.Join(2000);
}
catch { }
}
/// <summary>
/// Auto-reconnect loop
/// </summary>
private void ReconnectLoop()
{
while (isRunning)
{
if (!isConnected)
{
try
{
Connect();
}
catch
{
// Retry after 5 seconds
Thread.Sleep(5000);
}
}
else
{
Thread.Sleep(1000);
}
}
}
/// <summary>
/// Connect to server
/// </summary>
private void Connect()
{
try
{
tcpClient = new TcpClient();
tcpClient.Connect(serverIp, serverPort);
stream = tcpClient.GetStream();
isConnected = true;
// Notify connection status
ConnectionStatusChanged?.Invoke(this, true);
// Send connect message
var connectMsg = new ChatMessage
{
Type = MessageType.Connect,
NickName = nickName,
EmployeeId = employeeId,
HostName = hostName,
UserGroup = userGroup,
Timestamp = DateTime.Now
};
SendMessage(connectMsg);
// Start receive thread
receiveThread = new Thread(ReceiveLoop)
{
IsBackground = true,
Name = "ChatReceiveThread"
};
receiveThread.Start();
}
catch
{
Disconnect();
throw;
}
}
/// <summary>
/// Disconnect from server
/// </summary>
private void Disconnect()
{
if (!isConnected)
return;
isConnected = false;
try
{
stream?.Close();
tcpClient?.Close();
}
catch { }
// Notify connection status
ConnectionStatusChanged?.Invoke(this, false);
}
/// <summary>
/// Message receive loop
/// </summary>
private void ReceiveLoop()
{
byte[] buffer = new byte[8192];
try
{
while (isRunning && isConnected && tcpClient.Connected)
{
int bytesRead = stream.Read(buffer, 0, buffer.Length);
if (bytesRead == 0)
{
// Connection closed
break;
}
string jsonData = Encoding.UTF8.GetString(buffer, 0, bytesRead);
var message = JsonConvert.DeserializeObject<ChatMessage>(jsonData);
if (message != null)
{
// Filter messages by group (dev can see all)
// UserListResponse and system messages should always be received
if (userGroup == "dev" ||
message.UserGroup == userGroup ||
message.Type == MessageType.Notice ||
message.Type == MessageType.UserListResponse ||
message.Type == MessageType.Pong)
{
MessageReceived?.Invoke(this, message);
}
}
}
}
catch
{
// Connection error
}
finally
{
Disconnect();
}
}
/// <summary>
/// Send message
/// </summary>
public bool SendMessage(ChatMessage message)
{
if (!isConnected || tcpClient == null || !tcpClient.Connected)
return false;
try
{
message.NickName = nickName;
message.UserGroup = userGroup;
message.Timestamp = DateTime.Now;
string jsonData = JsonConvert.SerializeObject(message);
byte[] data = Encoding.UTF8.GetBytes(jsonData);
stream.Write(data, 0, data.Length);
stream.Flush();
return true;
}
catch
{
Disconnect();
return false;
}
}
/// <summary>
/// Send chat message
/// </summary>
public bool SendChatMessage(string content)
{
var message = new ChatMessage
{
Type = MessageType.Chat,
Content = content
};
return SendMessage(message);
}
/// <summary>
/// Request user list
/// </summary>
public bool RequestUserList()
{
var message = new ChatMessage
{
Type = MessageType.UserListRequest
};
return SendMessage(message);
}
public void Dispose()
{
Stop();
}
}
}

76
Project/ChatMessage.cs Normal file
View File

@@ -0,0 +1,76 @@
using System;
namespace Project
{
/// <summary>
/// Chat message type
/// </summary>
public enum MessageType
{
/// <summary>Client connect</summary>
Connect = 0,
/// <summary>Client disconnect</summary>
Disconnect = 1,
/// <summary>Normal chat message</summary>
Chat = 2,
/// <summary>Server notice</summary>
Notice = 3,
/// <summary>Whisper message</summary>
Whisper = 4,
/// <summary>User list request</summary>
UserListRequest = 5,
/// <summary>User list response</summary>
UserListResponse = 6,
/// <summary>Ping (keep alive)</summary>
Ping = 7,
/// <summary>Pong (ping response)</summary>
Pong = 8
}
/// <summary>
/// Chat message protocol
/// </summary>
[Serializable]
public class ChatMessage
{
/// <summary>Message type</summary>
public MessageType Type { get; set; }
/// <summary>Sender nickname</summary>
public string NickName { get; set; }
/// <summary>Sender employee ID (사번)</summary>
public string EmployeeId { get; set; }
/// <summary>Sender IP</summary>
public string IpAddress { get; set; }
/// <summary>Sender hostname</summary>
public string HostName { get; set; }
/// <summary>Message content</summary>
public string Content { get; set; }
/// <summary>Target employee ID (for 1:1 chat)</summary>
public string TargetEmployeeId { get; set; }
/// <summary>Target nickname (for whisper, null for broadcast)</summary>
public string TargetNickName { get; set; }
/// <summary>Send time</summary>
public DateTime Timestamp { get; set; }
/// <summary>User group (for filtering)</summary>
public string UserGroup { get; set; }
public ChatMessage()
{
Timestamp = DateTime.Now;
}
public override string ToString()
{
return $"[{Timestamp:HH:mm:ss}] {NickName}: {Content}";
}
}
}

184
Project/Dialog/fChat.Designer.cs generated Normal file
View File

@@ -0,0 +1,184 @@
namespace Project.Dialog
{
partial class fChat
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.txtChatDisplay = new System.Windows.Forms.TextBox();
this.txtInput = new System.Windows.Forms.TextBox();
this.btnSend = new System.Windows.Forms.Button();
this.lblStatus = new System.Windows.Forms.Label();
this.timerStatus = new System.Windows.Forms.Timer(this.components);
this.panelTop = new System.Windows.Forms.Panel();
this.panelBottom = new System.Windows.Forms.Panel();
this.lblRecipient = new System.Windows.Forms.Label();
this.cboRecipient = new System.Windows.Forms.ComboBox();
this.panelTop.SuspendLayout();
this.panelBottom.SuspendLayout();
this.SuspendLayout();
//
// txtChatDisplay
//
this.txtChatDisplay.BackColor = System.Drawing.Color.White;
this.txtChatDisplay.Dock = System.Windows.Forms.DockStyle.Fill;
this.txtChatDisplay.Font = new System.Drawing.Font("Consolas", 9F);
this.txtChatDisplay.Location = new System.Drawing.Point(0, 30);
this.txtChatDisplay.Multiline = true;
this.txtChatDisplay.Name = "txtChatDisplay";
this.txtChatDisplay.ReadOnly = true;
this.txtChatDisplay.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
this.txtChatDisplay.Size = new System.Drawing.Size(500, 340);
this.txtChatDisplay.TabIndex = 0;
//
// txtInput
//
this.txtInput.Dock = System.Windows.Forms.DockStyle.Fill;
this.txtInput.Font = new System.Drawing.Font("Malgun Gothic", 9F);
this.txtInput.Location = new System.Drawing.Point(0, 0);
this.txtInput.Name = "txtInput";
this.txtInput.Size = new System.Drawing.Size(420, 23);
this.txtInput.TabIndex = 1;
this.txtInput.KeyDown += new System.Windows.Forms.KeyEventHandler(this.txtInput_KeyDown);
//
// btnSend
//
this.btnSend.Dock = System.Windows.Forms.DockStyle.Right;
this.btnSend.Location = new System.Drawing.Point(420, 0);
this.btnSend.Name = "btnSend";
this.btnSend.Size = new System.Drawing.Size(80, 30);
this.btnSend.TabIndex = 2;
this.btnSend.Text = "Send";
this.btnSend.UseVisualStyleBackColor = true;
this.btnSend.Click += new System.EventHandler(this.btnSend_Click);
//
// lblStatus
//
this.lblStatus.AutoSize = true;
this.lblStatus.Dock = System.Windows.Forms.DockStyle.Fill;
this.lblStatus.Font = new System.Drawing.Font("Malgun Gothic", 9F, System.Drawing.FontStyle.Bold);
this.lblStatus.ForeColor = System.Drawing.Color.Green;
this.lblStatus.Location = new System.Drawing.Point(5, 5);
this.lblStatus.Name = "lblStatus";
this.lblStatus.Padding = new System.Windows.Forms.Padding(5);
this.lblStatus.Size = new System.Drawing.Size(83, 25);
this.lblStatus.TabIndex = 3;
this.lblStatus.Text = "Connected";
this.lblStatus.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// timerStatus
//
this.timerStatus.Enabled = true;
this.timerStatus.Interval = 1000;
this.timerStatus.Tick += new System.EventHandler(this.timerStatus_Tick);
//
// lblRecipient
//
this.lblRecipient.AutoSize = true;
this.lblRecipient.Dock = System.Windows.Forms.DockStyle.Left;
this.lblRecipient.Font = new System.Drawing.Font("Malgun Gothic", 9F);
this.lblRecipient.Location = new System.Drawing.Point(5, 5);
this.lblRecipient.Name = "lblRecipient";
this.lblRecipient.Padding = new System.Windows.Forms.Padding(0, 5, 5, 0);
this.lblRecipient.Size = new System.Drawing.Size(45, 20);
this.lblRecipient.TabIndex = 5;
this.lblRecipient.Text = "To:";
this.lblRecipient.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// cboRecipient
//
this.cboRecipient.Dock = System.Windows.Forms.DockStyle.Left;
this.cboRecipient.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cboRecipient.Font = new System.Drawing.Font("Malgun Gothic", 9F);
this.cboRecipient.FormattingEnabled = true;
this.cboRecipient.Location = new System.Drawing.Point(50, 5);
this.cboRecipient.Name = "cboRecipient";
this.cboRecipient.Size = new System.Drawing.Size(200, 23);
this.cboRecipient.TabIndex = 6;
//
// panelTop
//
this.panelTop.Controls.Add(this.cboRecipient);
this.panelTop.Controls.Add(this.lblRecipient);
this.panelTop.Controls.Add(this.lblStatus);
this.panelTop.Dock = System.Windows.Forms.DockStyle.Top;
this.panelTop.Location = new System.Drawing.Point(0, 0);
this.panelTop.Name = "panelTop";
this.panelTop.Padding = new System.Windows.Forms.Padding(5);
this.panelTop.Size = new System.Drawing.Size(500, 30);
this.panelTop.TabIndex = 4;
//
// panelBottom
//
this.panelBottom.Controls.Add(this.txtInput);
this.panelBottom.Controls.Add(this.btnSend);
this.panelBottom.Dock = System.Windows.Forms.DockStyle.Bottom;
this.panelBottom.Location = new System.Drawing.Point(0, 370);
this.panelBottom.Name = "panelBottom";
this.panelBottom.Size = new System.Drawing.Size(500, 30);
this.panelBottom.TabIndex = 5;
//
// fChat
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(500, 400);
this.Controls.Add(this.txtChatDisplay);
this.Controls.Add(this.panelBottom);
this.Controls.Add(this.panelTop);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Sizable;
this.KeyPreview = true;
this.MaximizeBox = true;
this.MinimizeBox = true;
this.MinimumSize = new System.Drawing.Size(400, 300);
this.Name = "fChat";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "Chat";
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.fChat_FormClosing);
this.Load += new System.EventHandler(this.fChat_Load);
this.panelTop.ResumeLayout(false);
this.panelTop.PerformLayout();
this.panelBottom.ResumeLayout(false);
this.panelBottom.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.TextBox txtChatDisplay;
private System.Windows.Forms.TextBox txtInput;
private System.Windows.Forms.Button btnSend;
private System.Windows.Forms.Label lblStatus;
private System.Windows.Forms.Timer timerStatus;
private System.Windows.Forms.Panel panelTop;
private System.Windows.Forms.Panel panelBottom;
private System.Windows.Forms.Label lblRecipient;
private System.Windows.Forms.ComboBox cboRecipient;
}
}

412
Project/Dialog/fChat.cs Normal file
View File

@@ -0,0 +1,412 @@
using FCOMMON;
using System;
using System.Drawing;
using System.Windows.Forms;
namespace Project.Dialog
{
/// <summary>
/// User info for recipient selection
/// </summary>
public class ChatUserInfo
{
public string EmployeeId { get; set; }
public string NickName { get; set; }
public string UserGroup { get; set; }
public override string ToString()
{
return $"{NickName} ({EmployeeId})";
}
}
public partial class fChat : fBase
{
private ChatClientService chatService;
private string targetEmployeeId;
private string targetNickName;
/// <summary>
/// Constructor for new chat or specific recipient
/// </summary>
public fChat(ChatClientService service, string targetEmployeeId = null, string targetNickName = null)
{
InitializeComponent();
this.chatService = service;
this.targetEmployeeId = targetEmployeeId;
this.targetNickName = targetNickName;
this.KeyDown += (s, e) => { if (e.KeyCode == Keys.Escape) this.Close(); };
}
private void fChat_Load(object sender, EventArgs e)
{
// Subscribe to message received event
if (chatService != null)
{
chatService.MessageReceived += OnMessageReceived;
}
// Load user list for recipient selection
LoadUserList();
// If target is specified, set it
if (!string.IsNullOrEmpty(targetEmployeeId))
{
SetRecipient(targetEmployeeId, targetNickName);
}
// Load recent messages (if any)
RefreshConnectionStatus();
// Request user list from server
if (chatService != null && chatService.IsConnected)
{
System.Diagnostics.Debug.WriteLine("[DEBUG] Requesting user list from server...");
chatService.RequestUserList();
}
else
{
System.Diagnostics.Debug.WriteLine($"[DEBUG] Cannot request user list - Service: {chatService != null}, Connected: {chatService?.IsConnected}");
}
}
/// <summary>
/// Load user list into combobox
/// </summary>
private void LoadUserList()
{
cboRecipient.Items.Clear();
// Add placeholder
cboRecipient.Items.Add(new ChatUserInfo
{
EmployeeId = "",
NickName = "Select recipient...",
UserGroup = ""
});
// In a real implementation, this would load from server
// For now, user will need to type employee ID manually
// The server's UserListResponse should populate this
cboRecipient.SelectedIndex = 0;
}
/// <summary>
/// Set specific recipient
/// </summary>
private void SetRecipient(string employeeId, string nickName)
{
targetEmployeeId = employeeId;
targetNickName = nickName;
// Try to find in combobox
bool found = false;
for (int i = 0; i < cboRecipient.Items.Count; i++)
{
var user = cboRecipient.Items[i] as ChatUserInfo;
if (user != null && user.EmployeeId == employeeId)
{
cboRecipient.SelectedIndex = i;
found = true;
break;
}
}
// If not found, add it
if (!found && !string.IsNullOrEmpty(employeeId))
{
var user = new ChatUserInfo
{
EmployeeId = employeeId,
NickName = nickName ?? employeeId,
UserGroup = ""
};
cboRecipient.Items.Add(user);
cboRecipient.SelectedItem = user;
}
// Update form title
this.Text = $"Chat - {nickName ?? employeeId}";
}
private void fChat_FormClosing(object sender, FormClosingEventArgs e)
{
// Unsubscribe events
if (chatService != null)
{
chatService.MessageReceived -= OnMessageReceived;
}
}
/// <summary>
/// Message received event handler
/// </summary>
private void OnMessageReceived(object sender, ChatMessage message)
{
if (InvokeRequired)
{
BeginInvoke(new Action(() => OnMessageReceived(sender, message)));
return;
}
// Get current recipient from combobox
var selectedUser = cboRecipient.SelectedItem as ChatUserInfo;
string currentTargetId = selectedUser?.EmployeeId ?? targetEmployeeId;
// Filter messages - only show messages between current user and target
string myEmployeeId = chatService.EmployeeId;
// Show message if:
// 1. It's from target to me
// 2. It's from me to target (echo from server)
// 3. It's a notice (broadcast)
bool shouldDisplay = false;
if (message.Type == MessageType.Notice)
{
shouldDisplay = true;
// If it's a join/leave notice, refresh user list
if (message.Content.Contains("has joined") || message.Content.Contains("has left"))
{
System.Diagnostics.Debug.WriteLine("[DEBUG] User joined/left, refreshing user list...");
if (chatService != null && chatService.IsConnected)
{
chatService.RequestUserList();
}
}
}
else if (message.Type == MessageType.UserListResponse)
{
// Handle user list response
HandleUserListResponse(message);
return;
}
else if (!string.IsNullOrEmpty(currentTargetId))
{
// From target to me
if (message.EmployeeId == currentTargetId && message.TargetEmployeeId == myEmployeeId)
{
shouldDisplay = true;
}
// From me to target (echo)
else if (message.EmployeeId == myEmployeeId && message.TargetEmployeeId == currentTargetId)
{
shouldDisplay = true;
}
}
if (shouldDisplay)
{
AppendMessage(message);
}
}
/// <summary>
/// Handle user list response from server
/// </summary>
private void HandleUserListResponse(ChatMessage message)
{
try
{
System.Diagnostics.Debug.WriteLine($"[DEBUG] UserListResponse received: {message.Content}");
// Parse user list from Content (format: "employeeId1:nickName1:userGroup1,employeeId2:nickName2:userGroup2,...")
if (string.IsNullOrEmpty(message.Content))
{
System.Diagnostics.Debug.WriteLine("[DEBUG] UserListResponse content is empty");
return;
}
cboRecipient.Items.Clear();
// Add placeholder
cboRecipient.Items.Add(new ChatUserInfo
{
EmployeeId = "",
NickName = "Select recipient...",
UserGroup = ""
});
string[] users = message.Content.Split(',');
System.Diagnostics.Debug.WriteLine($"[DEBUG] Parsing {users.Length} users");
foreach (var userStr in users)
{
if (string.IsNullOrWhiteSpace(userStr))
continue;
string[] parts = userStr.Split(':');
System.Diagnostics.Debug.WriteLine($"[DEBUG] User parts: {string.Join(", ", parts)}");
if (parts.Length >= 2)
{
string empId = parts[0].Trim();
string nick = parts[1].Trim();
System.Diagnostics.Debug.WriteLine($"[DEBUG] Processing user: {empId} - {nick}, My ID: {chatService.EmployeeId}");
// Don't add myself
if (empId != chatService.EmployeeId)
{
var user = new ChatUserInfo
{
EmployeeId = empId,
NickName = nick,
UserGroup = parts.Length > 2 ? parts[2].Trim() : ""
};
cboRecipient.Items.Add(user);
System.Diagnostics.Debug.WriteLine($"[DEBUG] Added user: {user}");
}
else
{
System.Diagnostics.Debug.WriteLine($"[DEBUG] Skipped myself: {empId}");
}
}
}
System.Diagnostics.Debug.WriteLine($"[DEBUG] Total items in combo: {cboRecipient.Items.Count}");
// Restore previous selection if exists
if (!string.IsNullOrEmpty(targetEmployeeId))
{
SetRecipient(targetEmployeeId, targetNickName);
}
else
{
cboRecipient.SelectedIndex = 0;
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[ERROR] HandleUserListResponse: {ex.Message}");
MessageBox.Show($"Error loading user list: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
/// <summary>
/// Append message to chat display
/// </summary>
private void AppendMessage(ChatMessage message)
{
string timeStr = message.Timestamp.ToString("HH:mm:ss");
string displayMsg = "";
switch (message.Type)
{
case MessageType.Chat:
displayMsg = $"[{timeStr}] {message.NickName}: {message.Content}";
break;
case MessageType.Notice:
displayMsg = $"[{timeStr}] [NOTICE] {message.Content}";
break;
case MessageType.Whisper:
displayMsg = $"[{timeStr}] [WHISPER from {message.NickName}] {message.Content}";
break;
default:
return;
}
txtChatDisplay.AppendText(displayMsg + Environment.NewLine);
txtChatDisplay.ScrollToCaret();
}
/// <summary>
/// Send button click
/// </summary>
private void btnSend_Click(object sender, EventArgs e)
{
SendMessage();
}
/// <summary>
/// Input text key down (Enter to send)
/// </summary>
private void txtInput_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
e.SuppressKeyPress = true;
SendMessage();
}
}
/// <summary>
/// Send chat message
/// </summary>
private void SendMessage()
{
string content = txtInput.Text.Trim();
if (string.IsNullOrEmpty(content))
return;
if (chatService == null || !chatService.IsConnected)
{
MessageBox.Show("Not connected to chat server.", "Chat", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
// Get target from combobox
var selectedUser = cboRecipient.SelectedItem as ChatUserInfo;
string currentTargetId = selectedUser?.EmployeeId ?? targetEmployeeId;
if (string.IsNullOrEmpty(currentTargetId))
{
MessageBox.Show("Please select a recipient.", "Chat", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
// Create message with target
var message = new ChatMessage
{
Type = MessageType.Chat,
Content = content,
TargetEmployeeId = currentTargetId
};
bool sent = chatService.SendMessage(message);
if (sent)
{
// Note: Server will echo the message back, so we don't display it here
// This prevents duplicate messages
// Clear input
txtInput.Text = "";
txtInput.Focus();
}
else
{
MessageBox.Show("Failed to send message.", "Chat", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
/// <summary>
/// Refresh connection status display
/// </summary>
private void RefreshConnectionStatus()
{
if (chatService != null && chatService.IsConnected)
{
lblStatus.Text = $"Connected - {chatService.NickName} ({chatService.UserGroup})";
lblStatus.ForeColor = Color.Green;
}
else
{
lblStatus.Text = "Disconnected";
lblStatus.ForeColor = Color.Red;
}
}
/// <summary>
/// Timer to update status periodically
/// </summary>
private void timerStatus_Tick(object sender, EventArgs e)
{
RefreshConnectionStatus();
}
}
}

64
Project/Dialog/fChat.resx Normal file
View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" msdata:Ordinal="5" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="timerStatus.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
</root>

View File

@@ -314,6 +314,10 @@
<Compile Include="Dialog\fMsgWindow.Designer.cs">
<DependentUpon>fMsgWindow.cs</DependentUpon>
</Compile>
<Compile Include="Dialog\fChat.cs" />
<Compile Include="Dialog\fChat.Designer.cs">
<DependentUpon>fChat.cs</DependentUpon>
</Compile>
<Compile Include="Dialog\fLogin.cs">
<SubType>Form</SubType>
</Compile>
@@ -478,6 +482,8 @@
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Pub.cs" />
<Compile Include="ChatMessage.cs" />
<Compile Include="ChatClientService.cs" />
<Compile Include="Setting.cs" />
<Compile Include="StateMachine\_TMDisplay.cs">
<SubType>Form</SubType>
@@ -507,6 +513,9 @@
<EmbeddedResource Include="Dialog\fMsgWindow.resx">
<DependentUpon>fMsgWindow.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Dialog\fChat.resx">
<DependentUpon>fChat.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Dialog\fLogin.resx">
<DependentUpon>fLogin.cs</DependentUpon>
</EmbeddedResource>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PublishUrlHistory>ftp://10.131.36.205:2121/Install/GroupWare/|ftp://10.131.36.205:2121/Install/|ftp://10.131.36.205/Install/|게시\</PublishUrlHistory>

View File

@@ -12,7 +12,7 @@ namespace Project.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.9.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.9.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));

View File

@@ -4,7 +4,7 @@
<Settings>
<Setting Name="gwcs" Type="(Connection string)" Scope="Application">
<DesignTimeValue Profile="(Default)">&lt;?xml version="1.0" encoding="utf-16"?&gt;
&lt;SerializableConnectionString xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"&gt;
&lt;SerializableConnectionString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"&gt;
&lt;ConnectionString&gt;Data Source=K4FASQL.kr.ds.amkor.com,50150;Initial Catalog=EE;Persist Security Info=True;User ID=eeadm;Password=uJnU8a8q&amp;amp;DJ+ug-D;Encrypt=False;TrustServerCertificate=True&lt;/ConnectionString&gt;
&lt;ProviderName&gt;System.Data.SqlClient&lt;/ProviderName&gt;
&lt;/SerializableConnectionString&gt;</DesignTimeValue>
@@ -12,7 +12,7 @@
</Setting>
<Setting Name="CS" Type="(Connection string)" Scope="Application">
<DesignTimeValue Profile="(Default)">&lt;?xml version="1.0" encoding="utf-16"?&gt;
&lt;SerializableConnectionString xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"&gt;
&lt;SerializableConnectionString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"&gt;
&lt;ConnectionString&gt;Data Source=K4FASQL.kr.ds.amkor.com,50150;Initial Catalog=EE;Persist Security Info=True;User ID=eeadm;Password=uJnU8a8q&amp;amp;DJ+ug-D;Encrypt=False;TrustServerCertificate=True&lt;/ConnectionString&gt;
&lt;ProviderName&gt;System.Data.SqlClient&lt;/ProviderName&gt;
&lt;/SerializableConnectionString&gt;</DesignTimeValue>

View File

@@ -3,16 +3,26 @@
<configSections>
</configSections>
<connectionStrings>
<add name="Project.Properties.Settings.gwcs" connectionString="Data Source=K4FASQL.kr.ds.amkor.com,50150;Initial Catalog=EE;Persist Security Info=True;User ID=eeadm;Password=uJnU8a8q&amp;DJ+ug-D;Encrypt=False;TrustServerCertificate=True" providerName="System.Data.SqlClient" />
<add name="EEEntities" connectionString="metadata=res://*/Model1.csdl|res://*/Model1.ssdl|res://*/Model1.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=K4FASQL.kr.ds.amkor.com,50150;initial catalog=EE;persist security info=True;user id=eeadm;password=uJnU8a8q&amp;DJ+ug-D!;MultipleActiveResultSets=True;App=EntityFramework&quot;TrustServerCertificate=True" providerName="System.Data.EntityClient" />
<add name="EEEntities1" connectionString="metadata=res://*/ModelMain.csdl|res://*/ModelMain.ssdl|res://*/ModelMain.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=K4FASQL.kr.ds.amkor.com,50150;initial catalog=EE;user id=eeadm;password=uJnU8a8q&amp;DJ+ug-D!;connect timeout=30;encrypt=False;trustservercertificate=True;MultipleActiveResultSets=True;App=EntityFramework&quot;" providerName="System.Data.EntityClient" />
<add name="EEEntitiesMain" connectionString="metadata=res://*/AdoNetEFMain.csdl|res://*/AdoNetEFMain.ssdl|res://*/AdoNetEFMain.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=K4FASQL.kr.ds.amkor.com,50150;initial catalog=EE;user id=eeadm;password=uJnU8a8q&amp;DJ+ug-D!;connect timeout=30;encrypt=False;trustservercertificate=True;MultipleActiveResultSets=True;App=EntityFramework&quot;" providerName="System.Data.EntityClient" />
<add name="S1ACCESS300Entities" connectionString="metadata=res://*/Model1.csdl|res://*/Model1.ssdl|res://*/Model1.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=10.141.18.50;initial catalog=S1ACCESS300;persist security info=True;user id=amkoruser;password=AmkorUser!;MultipleActiveResultSets=True;App=EntityFramework&quot;TrustServerCertificate=True" providerName="System.Data.EntityClient" />
<add name="EEEntitiesPurchase" connectionString="metadata=res://*/ModelPurchase.csdl|res://*/ModelPurchase.ssdl|res://*/ModelPurchase.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=K4FASQL.kr.ds.amkor.com,50150;initial catalog=EE;persist security info=True;user id=eeadm;password=uJnU8a8q&amp;DJ+ug-D!;MultipleActiveResultSets=True;App=EntityFramework&quot;TrustServerCertificate=True" providerName="System.Data.EntityClient" />
<add name="EEEntitiesCommon" connectionString="metadata=res://*/ModelCommon.csdl|res://*/ModelCommon.ssdl|res://*/ModelCommon.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=K4FASQL.kr.ds.amkor.com,50150;initial catalog=EE;persist security info=True;user id=eeadm;password=uJnU8a8q&amp;DJ+ug-D!;MultipleActiveResultSets=True;App=EntityFramework&quot;TrustServerCertificate=True" providerName="System.Data.EntityClient" />
<add name="EEEntitiesJobreport" connectionString="metadata=res://*/ModelJobreport.csdl|res://*/ModelJobreport.ssdl|res://*/ModelJobreport.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=K4FASQL.kr.ds.amkor.com,50150;initial catalog=EE;persist security info=True;user id=eeadm;password=uJnU8a8q&amp;DJ+ug-D!;MultipleActiveResultSets=True;App=EntityFramework&quot;TrustServerCertificate=True" providerName="System.Data.EntityClient" />
<add name="EEEntitiesProject" connectionString="metadata=res://*/ModelProject.csdl|res://*/ModelProject.ssdl|res://*/ModelProject.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=K4FASQL.kr.ds.amkor.com,50150;initial catalog=EE;persist security info=True;user id=eeadm;password=uJnU8a8q&amp;DJ+ug-D!;MultipleActiveResultSets=True;App=EntityFramework&quot;TrustServerCertificate=True" providerName="System.Data.EntityClient" />
<add name="Project.Properties.Settings.CS" connectionString="Data Source=K4FASQL.kr.ds.amkor.com,50150;Initial Catalog=EE;Persist Security Info=True;User ID=eeadm;Password=uJnU8a8q&amp;DJ+ug-D;Encrypt=False;TrustServerCertificate=True" providerName="System.Data.SqlClient" />
<add name="Project.Properties.Settings.gwcs" connectionString="Data Source=K4FASQL.kr.ds.amkor.com,50150;Initial Catalog=EE;Persist Security Info=True;User ID=eeadm;Password=uJnU8a8q&amp;DJ+ug-D;Encrypt=False;TrustServerCertificate=True"
providerName="System.Data.SqlClient" />
<add name="EEEntities" connectionString="metadata=res://*/Model1.csdl|res://*/Model1.ssdl|res://*/Model1.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=K4FASQL.kr.ds.amkor.com,50150;initial catalog=EE;persist security info=True;user id=eeadm;password=uJnU8a8q&amp;DJ+ug-D!;MultipleActiveResultSets=True;App=EntityFramework&quot;TrustServerCertificate=True"
providerName="System.Data.EntityClient" />
<add name="EEEntities1" connectionString="metadata=res://*/ModelMain.csdl|res://*/ModelMain.ssdl|res://*/ModelMain.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=K4FASQL.kr.ds.amkor.com,50150;initial catalog=EE;user id=eeadm;password=uJnU8a8q&amp;DJ+ug-D!;connect timeout=30;encrypt=False;trustservercertificate=True;MultipleActiveResultSets=True;App=EntityFramework&quot;"
providerName="System.Data.EntityClient" />
<add name="EEEntitiesMain" connectionString="metadata=res://*/AdoNetEFMain.csdl|res://*/AdoNetEFMain.ssdl|res://*/AdoNetEFMain.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=K4FASQL.kr.ds.amkor.com,50150;initial catalog=EE;user id=eeadm;password=uJnU8a8q&amp;DJ+ug-D!;connect timeout=30;encrypt=False;trustservercertificate=True;MultipleActiveResultSets=True;App=EntityFramework&quot;"
providerName="System.Data.EntityClient" />
<add name="S1ACCESS300Entities" connectionString="metadata=res://*/Model1.csdl|res://*/Model1.ssdl|res://*/Model1.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=10.141.18.50;initial catalog=S1ACCESS300;persist security info=True;user id=amkoruser;password=AmkorUser!;MultipleActiveResultSets=True;App=EntityFramework&quot;TrustServerCertificate=True"
providerName="System.Data.EntityClient" />
<add name="EEEntitiesPurchase" connectionString="metadata=res://*/ModelPurchase.csdl|res://*/ModelPurchase.ssdl|res://*/ModelPurchase.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=K4FASQL.kr.ds.amkor.com,50150;initial catalog=EE;persist security info=True;user id=eeadm;password=uJnU8a8q&amp;DJ+ug-D!;MultipleActiveResultSets=True;App=EntityFramework&quot;TrustServerCertificate=True"
providerName="System.Data.EntityClient" />
<add name="EEEntitiesCommon" connectionString="metadata=res://*/ModelCommon.csdl|res://*/ModelCommon.ssdl|res://*/ModelCommon.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=K4FASQL.kr.ds.amkor.com,50150;initial catalog=EE;persist security info=True;user id=eeadm;password=uJnU8a8q&amp;DJ+ug-D!;MultipleActiveResultSets=True;App=EntityFramework&quot;TrustServerCertificate=True"
providerName="System.Data.EntityClient" />
<add name="EEEntitiesJobreport" connectionString="metadata=res://*/ModelJobreport.csdl|res://*/ModelJobreport.ssdl|res://*/ModelJobreport.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=K4FASQL.kr.ds.amkor.com,50150;initial catalog=EE;persist security info=True;user id=eeadm;password=uJnU8a8q&amp;DJ+ug-D!;MultipleActiveResultSets=True;App=EntityFramework&quot;TrustServerCertificate=True"
providerName="System.Data.EntityClient" />
<add name="EEEntitiesProject" connectionString="metadata=res://*/ModelProject.csdl|res://*/ModelProject.ssdl|res://*/ModelProject.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=K4FASQL.kr.ds.amkor.com,50150;initial catalog=EE;persist security info=True;user id=eeadm;password=uJnU8a8q&amp;DJ+ug-D!;MultipleActiveResultSets=True;App=EntityFramework&quot;TrustServerCertificate=True"
providerName="System.Data.EntityClient" />
<add name="Project.Properties.Settings.CS" connectionString="Data Source=K4FASQL.kr.ds.amkor.com,50150;Initial Catalog=EE;Persist Security Info=True;User ID=eeadm;Password=uJnU8a8q&amp;DJ+ug-D;Encrypt=False;TrustServerCertificate=True"
providerName="System.Data.SqlClient" />
</connectionStrings>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">

View File

@@ -41,6 +41,7 @@
this.toolStripStatusLabel1 = new System.Windows.Forms.ToolStripStatusLabel();
this.sbLoginUseTime = new System.Windows.Forms.ToolStripStatusLabel();
this.sbWeb = new System.Windows.Forms.ToolStripStatusLabel();
this.sbChat = new System.Windows.Forms.ToolStripStatusLabel();
this.menuStrip1 = new System.Windows.Forms.MenuStrip();
this.btSetting = new System.Windows.Forms.ToolStripMenuItem();
this.ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
@@ -191,7 +192,8 @@
this.sbLogin,
this.toolStripStatusLabel1,
this.sbLoginUseTime,
this.sbWeb});
this.sbWeb,
this.sbChat});
this.statusStrip1.Location = new System.Drawing.Point(1, 622);
this.statusStrip1.Name = "statusStrip1";
this.statusStrip1.Size = new System.Drawing.Size(1094, 22);
@@ -228,6 +230,15 @@
this.sbWeb.Size = new System.Drawing.Size(31, 17);
this.sbWeb.Text = "WEB";
//
// sbChat
//
this.sbChat.ForeColor = System.Drawing.Color.Gray;
this.sbChat.IsLink = true;
this.sbChat.Name = "sbChat";
this.sbChat.Size = new System.Drawing.Size(42, 17);
this.sbChat.Text = "Notice";
this.sbChat.Click += new System.EventHandler(this.sbChat_Click);
//
// menuStrip1
//
this.menuStrip1.Font = new System.Drawing.Font("맑은 고딕", 10F);
@@ -1343,6 +1354,7 @@
private System.Windows.Forms.ToolStripSeparator toolStripMenuItem17;
private System.Windows.Forms.ToolStripMenuItem webview2TestToolStripMenuItem;
private System.Windows.Forms.ToolStripStatusLabel sbWeb;
private System.Windows.Forms.ToolStripStatusLabel sbChat;
private System.Windows.Forms.ToolStripSeparator toolStripMenuItem18;
}
}

View File

@@ -20,11 +20,20 @@ namespace Project
string SearchKey = string.Empty;
private IDisposable webApp;
bool webok = false;
private ChatClientService chatService;
private System.Windows.Forms.Timer chatBlinkTimer;
private bool chatHasNewMessage = false;
private Dictionary<string, string> chatMessageSenders = new Dictionary<string, string>(); // employeeId -> nickName
public fMain()
{
InitializeComponent();
// Initialize chat blink timer
chatBlinkTimer = new System.Windows.Forms.Timer();
chatBlinkTimer.Interval = 500; // 500ms blink
chatBlinkTimer.Tick += ChatBlinkTimer_Tick;
this.KeyDown += (s1, e1) =>
{
if (e1.KeyCode == Keys.F12) btSetting.PerformClick();
@@ -93,6 +102,10 @@ namespace Project
}
}
// Stop chat service
chatService?.Stop();
chatBlinkTimer?.Stop();
FCOMMON.Pub.log.Add("Program Close");
FCOMMON.Pub.log.Flush();
}
@@ -193,6 +206,9 @@ namespace Project
Func_Login();
// Start chat service after login
StartChatService();
///즐겨찾기 목록 갱신
Update_FavoriteSite();
@@ -1558,8 +1574,214 @@ namespace Project
fdashboard.RefreshView();
Console.WriteLine( "view update");
}
}
}
#region Chat Functions
/// <summary>
/// Start chat service after login
/// </summary>
private void StartChatService()
{
try
{
// Get user info
string nickName = FCOMMON.info.Login.nameK ?? "Unknown";
string employeeId = FCOMMON.info.Login.no ?? "Unknown";
string userGroup = FCOMMON.info.Login.gcode ?? "default";
// Initialize chat service
//var chatServerIP = "127.0.0.1";// : "10.131.11.45";
var chatServerIP = "10.131.11.45";
chatService = new ChatClientService(chatServerIP, 5000);
chatService.ConnectionStatusChanged += ChatService_ConnectionStatusChanged;
chatService.MessageReceived += ChatService_MessageReceived;
// Start background service
chatService.Start(nickName, employeeId, userGroup);
FCOMMON.Pub.log.AddI($"Chat service started: {nickName} ({employeeId}, {userGroup})");
}
catch (Exception ex)
{
FCOMMON.Pub.log.AddE($"Failed to start chat service: {ex.Message}");
}
}
/// <summary>
/// Chat connection status changed
/// </summary>
private void ChatService_ConnectionStatusChanged(object sender, bool isConnected)
{
if (InvokeRequired)
{
BeginInvoke(new Action(() => ChatService_ConnectionStatusChanged(sender, isConnected)));
return;
}
if (isConnected)
{
sbChat.ForeColor = Color.Green;
sbChat.Text = "Notice ●";
}
else
{
sbChat.ForeColor = Color.Gray;
sbChat.Text = "Notice ○";
}
}
/// <summary>
/// Chat message received
/// </summary>
private void ChatService_MessageReceived(object sender, ChatMessage message)
{
if (InvokeRequired)
{
BeginInvoke(new Action(() => ChatService_MessageReceived(sender, message)));
return;
}
// Track message senders (only messages directed to me)
string myEmployeeId = chatService.EmployeeId;
if ((message.Type == MessageType.Chat || message.Type == MessageType.Whisper) &&
!string.IsNullOrEmpty(message.EmployeeId) &&
message.EmployeeId != myEmployeeId &&
message.TargetEmployeeId == myEmployeeId)
{
// Add or update sender
if (!chatMessageSenders.ContainsKey(message.EmployeeId))
{
chatMessageSenders[message.EmployeeId] = message.NickName ?? message.EmployeeId;
}
// Start blinking
chatHasNewMessage = true;
chatBlinkTimer.Start();
}
}
/// <summary>
/// Chat blink timer tick
/// </summary>
private void ChatBlinkTimer_Tick(object sender, EventArgs e)
{
if (!chatHasNewMessage)
{
chatBlinkTimer.Stop();
sbChat.ForeColor = chatService?.IsConnected == true ? Color.Green : Color.Gray;
return;
}
// Toggle color
sbChat.ForeColor = sbChat.ForeColor == Color.Red ? Color.Green : Color.Red;
}
/// <summary>
/// sbChat status label click event
/// </summary>
private void sbChat_Click(object sender, EventArgs e)
{
// Stop blinking
chatHasNewMessage = false;
chatBlinkTimer.Stop();
sbChat.ForeColor = chatService?.IsConnected == true ? Color.Green : Color.Gray;
// Show chat dialog
if (chatService == null)
{
MessageBox.Show("Chat service is not running.", "Chat", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
// If there are senders who sent messages, show selection dialog
if (chatMessageSenders.Count > 0)
{
// Create simple selection form
using (var selectForm = new Form())
{
selectForm.Text = "Select Sender";
selectForm.Size = new Size(400, 300);
selectForm.StartPosition = FormStartPosition.CenterParent;
selectForm.FormBorderStyle = FormBorderStyle.FixedDialog;
selectForm.MaximizeBox = false;
selectForm.MinimizeBox = false;
var listBox = new ListBox
{
Dock = DockStyle.Fill,
Font = new Font("Malgun Gothic", 10)
};
var buttonPanel = new Panel
{
Dock = DockStyle.Bottom,
Height = 50
};
var btnOk = new Button
{
Text = "Open Chat",
DialogResult = DialogResult.OK,
Size = new Size(100, 30),
Location = new Point(140, 10)
};
var btnNew = new Button
{
Text = "New Chat",
DialogResult = DialogResult.Retry,
Size = new Size(100, 30),
Location = new Point(250, 10)
};
buttonPanel.Controls.Add(btnOk);
buttonPanel.Controls.Add(btnNew);
selectForm.Controls.Add(listBox);
selectForm.Controls.Add(buttonPanel);
selectForm.AcceptButton = btnOk;
// Populate list with senders
foreach (var sender2 in chatMessageSenders)
{
listBox.Items.Add(new { EmployeeId = sender2.Key, NickName = sender2.Value, Display = $"{sender2.Value} ({sender2.Key})" });
}
listBox.DisplayMember = "Display";
if (listBox.Items.Count > 0)
listBox.SelectedIndex = 0;
var result = selectForm.ShowDialog(this);
if (result == DialogResult.OK && listBox.SelectedItem != null)
{
// Open chat with selected sender
dynamic selected = listBox.SelectedItem;
var chatForm = new Dialog.fChat(chatService, selected.EmployeeId, selected.NickName);
chatForm.Show();
// Remove from senders list (chat opened)
chatMessageSenders.Remove(selected.EmployeeId);
}
else if (result == DialogResult.Retry)
{
// Open new chat (user will select recipient in chat window)
var chatForm = new Dialog.fChat(chatService);
chatForm.Show();
}
}
}
else
{
// No senders, open new chat
var chatForm = new Dialog.fChat(chatService);
chatForm.Show();
}
}
#endregion
}
}

View File

@@ -184,16 +184,16 @@
<data name="mn_purchase.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m
dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAIwSURBVDhPpVLbSqJRGPVqruY9Zp7gf4F5jbkSEhFE
00pFS4IyFSVTxHN4yAIPlCc8lGCpReJFqKAoaKJzNVeSMIgya/begw5SNAxzsfbP/vjWWt+3/s0D8F94
s/geQqEQTk9P4Xa7Ybfbv/Kq1SooyuUySqUSisUiCoUCstksUqkULi8vEY1GcXFxgWAwyHrn8znG4zEs
FkuLV6lUsFgsMJvN3sXLywt8Pt+3yWSCTqeD4+PjgdFoFPHu7u5YYTQa4eHhAcPhELQ2GAxwfX2NXq+H
dDrNemKxGFqtFpvIYDB8oSuxvW5ubnB/f49MJsPd3t4iHo9zlHx2dsbRNTweD0fJVquVC4fDbP+Dg4PP
K4FcLodGo8Ey6Pf7LAPqTMntdnvlTATx9PRE3bG3t/dxJUBxdXWF6XSKfD4PkjSXTCapE0fHJWFxlHx0
dMR5vV5KJpTfvJUAadTUajV0u11QMnWORCJoNptUkDmTVVCv16FUKn++EqAIBAI0aTidTthsNpo0SNI4
PDzE/v4+tFotEokEtra23hb4GxQKxQe5XA6JRPJ9WWOH3+//RMZzORwOmjTMZjP0ej1Nmrmq1Wrs7OxA
JpORdvBEItF0TYCEpaHfk5MTqclkeiZk6bJhic3NTalYLH4mZKlAIPixrLODvGkNSZr9rsfHR5yfn7Ok
VSoVtre3QchwuVzsGZNe8Pn83prAEjqdjoW1u7v7agKhUCjd2NigZHL9U19r+neA9wvhROqXtIFlogAA
dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAIwSURBVDhPpZLPahpRGMVdddX3aJ9gXqCv0ZVgkAGJ
ozEqMUpAExUlE2UwziQhZtRANMRoxP+g+SeKi6BCgkJM0K66EoUiSk+5FyJYSwPt4jBwued3vu/cUQBQ
/I+WDt6TLMs4OjqCKIoQBOGr4u7uDkQ3Nzcol8solUrI5/PIZDJIpVK4uLhALBbD6ekpTk5O6N3pdIrB
YACe59uK29tbzGYzTCaTv2o0GuHw8PDbcDjE09MTdnd3e263m1VcX1/Tg36/j2q1itfXV5CzXq+HQqGA
breLq6sreicej6PdbtOJXC7Xl3kHxWIR9/f3SKfTTKVSwfn5OUPM4XCYIWtIksQQs8/nYyKRCN3fbrd/
ngOy2SyazSbt4Pn5mXZAkon58fFxnhwOh/Hw8EDSYbVaP84BRIlEAuPxGLlcDrIsM8lkkiQxZFye5xli
3tnZYQ4ODogZb745IBaLWer1OjqdDoiZJJ+dnaHVahEgTZYkCY1GAyaT6ecSgCgUCpGmsb+/D7/fT5qG
2+2Gw+HA1tYWbDYbLi8vsba29mfAezIajR/0ej1WV1e/LwCOj48/SZIUDAQCpGl4vV44nU7SNE3d2NjA
+vo6dDod3Z1l2fECQBRFC/nu7e1pPR7Pi9Pp1P6eznGcVqPRvLAsq11ZWfmxABAEwcLzPH2uWq2GaDRK
mzabzTAYDOA4DsFgkP7GgiBAqVR2FwBv2t7epmVtbm4uTaBWq7UqlYqY50+4BPgX/QLhROqXAnK3iwAA
AABJRU5ErkJggg==
</value>
</data>
@@ -254,34 +254,34 @@
<data name="휴가관리ToolStripMenuItem.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m
dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAALrSURBVDhPbZLZU9NXGIb5V3rn2FuttiJCUVtxuanI
AGFJ2EQWl1GMIERASkQnhCQQQogRDSEhLHEERRmlUBKWH0sSI+DODxEdtcNF9danBzrOqOHiPVfnfd7z
fe+JAqIOFl/98UCRbW5/kc23v9BGQmEr+wqEjln5Xei3/Bb2HrWwJ6+Z+GwjMWkXX6z51rR+CHODMH9W
mwdRVXWhrOwi84KHDI2HdE0naRUuFOddiHvE55qJVmiF7SuAMM+qzfe461tg/sVb5p6/Ye7ZCuGnyzx8
8orgwhKBeZnXb96zurrK5pik7wCFrYvtI4850dBPhkhOr3CvS1HuIlUkp5R1kHzOuT5WXHZTJCChoFV2
jz+n8X6Yu+EVbgdX6A8sc2vmFV5pie6JRdxjL7k29ISEgpZIgFiW7PQ9paTDj3XoMcY7QXReibqeSWo9
41R7JGq9QayDC2KplkiA2LTsECOccgxjGgix/M8nlj58RBZafP+Rl+/+ReOWaBqYE41sABA1yfaheY7b
76PvD6Ptlqh0jVHe7qe0bZSSNh9lHRL6vrCoszkSIDqWrYOPyG8ZoO5WmNreEFXdAco7Z1E7pzntkDjj
mKLuZkj8BXMkYE+eRW66EyLH0Ed1TxCNZ5ZS1zQl7RInr09SdHWcIvs4f/YE2J2zQQu7c5tlQ1+AzMu9
nHfPcNY5xekbEsXXJjhmGyO3xc9Rq58qAf01uzESEJ9jlnXeGVJrOsVT/08tEKlrpiyLj3TTCErTqNjJ
FHFZpkiA+ByytksiqcJJsX2C/LVUix+l+W8UhhGS64dJ1Q2jbpskVrUBIDarSa5xT3JYfYM88VxV8+h6
aop+mCNX/uKPSw9I1A5xxjbBLpWRTdGJ8jeAXapGucYzxaXeIFpviJruIJWdATQds5Q5ZkSV05y1i9Es
Y8Qojfyw5VDbN4AYpal1Z6aJ6EwjOzIM/JJu4Oe0Bran6dmu0LMttZ6fUurZmqJja7Lu5hczEPUfnqi7
iiOVMjYAAAAASUVORK5CYII=
dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAL2SURBVDhPbdHJU9t1HMbx/ivenHq1tirSImVHL7V2
gLAkrAkErVNoypZCRVJ0QkhCQjbS2BASwpJOoaUyLQVJCPxYkhhZWtvyo0gdW4eDePXtEMcDhsPzuT2v
Z77zPQGc+KR+4J18pWM9T+kI5tU5yK2zk1NrJ0dhI1thI0tuJbPGQkZ1P+kVBlKLv35+2DtM4uQrHb15
SsffKvM0so4RpO0jlF33U6r2U6IeprjNi6TFS77SQXqVmRSJhiNAntKxpjL/wIPgJhvPf2P92SvWf9kj
/nSXn568JLq5Q2RD5NdXr9nf3+dk6qX/AXX27cG5Lb7snaT0up+SNl8iklYvRS1eCpuHKLjmSTwrrcKU
DOTW2kVf+Bl9D+M8iO9xL7rHZGSXu6svCQg7jC5u41t4wa2ZJ+TWWpOBHIVN9ASf0jgUwjazheF+FG1A
oHtsiS5/mBt+ga5AFNv0JtkKSzKQrbCJ7rktvnLPYpyKsfvHX+y8OUB8c8D26wNe/P4nap+AaWqdLPkx
QJbcKjpnNvjC+RDdZBzNqEC7d4HWwRBNrnkaXUGahwR0E3Eya/qTgcwai2ib/hm5dYruu3G6xmN0jEZo
HV5D5Vnhilugwb1M950YGdXmZCCj2iKa7seo1E9wYyyK2r9Gk3eFxkGBy98voRwIo3SG+WYswvnKY37h
fFW/qJ+IUPbtOC2+Va56lrlyW6D+1iIKxwJV1hA1thAd3hU+ruhLBtIrzaI2sEpR5zAN7n9XawfCiVK5
JUiJcQ6pcZ7WwWXSyo3JQFqFSdSMCFxq81DvXER+uGoJITX/iEQ/R0HPLEXaWVSuJc7JjgHOlZvETt8S
n6luU20NIeufT6wW6mb5/LvHXLj5iIuaGRoci5yVGXg75aJ4BDgr6xM7/cvcHI+iCcToHI3SPhxBPbRG
s3uVJtcKV50Cly0LpEoNvPXup64jQKrUaP+ozEhKmYEPS/V8UKLn/eJezhTrOCPRcbqoh/cKezhVqOVU
gfbOf+XD/AOeqLuKeIA3fgAAAABJRU5ErkJggg==
</value>
</data>
<data name="personalInventoryToolStripMenuItem.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m
dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHuSURBVDhPfZJbbxJRFIXPn+irpv+CZOIv88XamD7W
Ng1t41trNHINhHsgAYFwC4SrzAyIIwEBMWkTdKZyyfLsQxw7HezDeph99vrW3mcO63Q6aLfbaDabaDQa
qNfrqNVqqFarqFQqewDYY2KtVgubzcam0WgkIMVi8VEIo2QyDIdDkaxpmgmhGp8CuVzuvxBGI1MzpVGh
XC5XF4sFVquVqBOwUCggnU7vhDBK/dvIzb/5yEPDMPD92y/k4zNxNhgMkM1mkUqlbBBGydREWq/XIPOP
mYHzQxUnzxXkYltIv9+nKRCPxy0QxnfU5/O5aFoulyLZ+VLF1fEA7840XBz2TIiqqjQFwuGwCWGlUukZ
31GfzbZNcuNGJF+ffMGHi682iKIoNAUCgYCACEo+n5f4jvp0OhVNvfYtnAcK3p7+gzgPVHyMbM+73S5N
AZ/Pt2fukslkJL6jPplMbJD3Tg1vjvq4PPrE/85aTBGNRuHxeJ6aABLfT0omk/p4PDYhZy9kXL7q4fq0
h58L4755nzwWACmRSEixWEynl0gQtXWDq9efcWesTLPb7RZmkg1AikQiUigU0uklEoREZl63mEkW430F
g0EHv2mdHpgsyzvNJMvHQ/n9fofX69XJ7HK5bGaSrfBQPNXBzU92nQFgfwCJli/+LKS33wAAAABJRU5E
dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHuSURBVDhPhZLdb9JgGMX7T+xW439BQvzLvHEuZpdz
y4JbvNuMxpaPlED5CE2KhbS0gYwvgYKIBATEZCZoqxRyzPMS6hCGF+fm6XN+57xvX65er6NWq6FSqaBc
LuPm5galUgnFYhGmaR4A4PaJq1arWC6XWxoMBgyi6/peCEfJZOj3+yy51+t5EJqZpolcLncvhKPKtExp
NDAMozibzeC6LpsTUNM0KIqyE8JR6nrRMIzfuq73HcfB1y8/kU9N2LdutwtVVSHL8haEo+R15cViATJ/
mzh4eWTh9EkLueQK0ul0qAVSqdQGhDNN055Op2xpPp+z5MAzC1cnXbw57+HiqO1BLMuiFojH4x6EKxQK
jzVNsyeT1VKzfMuSr08/4d3F5y1Iq9WiFhBFkUEYJZ/P+1VVtcfjMVtq174jcNjC67O/kMChhffS6nuj
0aAWCIfDB95ZstmsX1EUezQabUHeBnp4ddzB5fEHuO6CtUgkEggGgw83blSWZX8mk7GHw6EHOX/axOXz
Nq7P2vgxc+6aH3lHuKt0Ou1PJpM2vUR2cdVbXL34iF+O65kFQWDmnQCSJEn+WCxm00tc/2IyS5K0Yb4X
QIpGoz5RFG16YM1mc6d5L4AUiUR8oVDIJjPP81vm/wJIgiD4eJ5/8O98rT+Jli/+ECJFiAAAAABJRU5E
rkJggg==
</value>
</data>
@@ -369,32 +369,32 @@
<data name="toolStripMenuItem7.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m
dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAIwSURBVDhPpVLbSqJRGPVqruY9Zp7gf4F5jbkSEhFE
00pFS4IyFSVTxHN4yAIPlCc8lGCpReJFqKAoaKJzNVeSMIgya/begw5SNAxzsfbP/vjWWt+3/s0D8F94
s/geQqEQTk9P4Xa7Ybfbv/Kq1SooyuUySqUSisUiCoUCstksUqkULi8vEY1GcXFxgWAwyHrn8znG4zEs
FkuLV6lUsFgsMJvN3sXLywt8Pt+3yWSCTqeD4+PjgdFoFPHu7u5YYTQa4eHhAcPhELQ2GAxwfX2NXq+H
dDrNemKxGFqtFpvIYDB8oSuxvW5ubnB/f49MJsPd3t4iHo9zlHx2dsbRNTweD0fJVquVC4fDbP+Dg4PP
K4FcLodGo8Ey6Pf7LAPqTMntdnvlTATx9PRE3bG3t/dxJUBxdXWF6XSKfD4PkjSXTCapE0fHJWFxlHx0
dMR5vV5KJpTfvJUAadTUajV0u11QMnWORCJoNptUkDmTVVCv16FUKn++EqAIBAI0aTidTthsNpo0SNI4
PDzE/v4+tFotEokEtra23hb4GxQKxQe5XA6JRPJ9WWOH3+//RMZzORwOmjTMZjP0ej1Nmrmq1Wrs7OxA
JpORdvBEItF0TYCEpaHfk5MTqclkeiZk6bJhic3NTalYLH4mZKlAIPixrLODvGkNSZr9rsfHR5yfn7Ok
VSoVtre3QchwuVzsGZNe8Pn83prAEjqdjoW1u7v7agKhUCjd2NigZHL9U19r+neA9wvhROqXtIFlogAA
dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAIwSURBVDhPpZLPahpRGMVdddX3aJ9gXqCv0ZVgkAGJ
ozEqMUpAExUlE2UwziQhZtRANMRoxP+g+SeKi6BCgkJM0K66EoUiSk+5FyJYSwPt4jBwued3vu/cUQBQ
/I+WDt6TLMs4OjqCKIoQBOGr4u7uDkQ3Nzcol8solUrI5/PIZDJIpVK4uLhALBbD6ekpTk5O6N3pdIrB
YACe59uK29tbzGYzTCaTv2o0GuHw8PDbcDjE09MTdnd3e263m1VcX1/Tg36/j2q1itfXV5CzXq+HQqGA
breLq6sreicej6PdbtOJXC7Xl3kHxWIR9/f3SKfTTKVSwfn5OUPM4XCYIWtIksQQs8/nYyKRCN3fbrd/
ngOy2SyazSbt4Pn5mXZAkon58fFxnhwOh/Hw8EDSYbVaP84BRIlEAuPxGLlcDrIsM8lkkiQxZFye5xli
3tnZYQ4ODogZb745IBaLWer1OjqdDoiZJJ+dnaHVahEgTZYkCY1GAyaT6ecSgCgUCpGmsb+/D7/fT5qG
2+2Gw+HA1tYWbDYbLi8vsba29mfAezIajR/0ej1WV1e/LwCOj48/SZIUDAQCpGl4vV44nU7SNE3d2NjA
+vo6dDod3Z1l2fECQBRFC/nu7e1pPR7Pi9Pp1P6eznGcVqPRvLAsq11ZWfmxABAEwcLzPH2uWq2GaDRK
mzabzTAYDOA4DsFgkP7GgiBAqVR2FwBv2t7epmVtbm4uTaBWq7UqlYqY50+4BPgX/QLhROqXAnK3iwAA
AABJRU5ErkJggg==
</value>
</data>
<data name="toolStripMenuItem16.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m
dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAIwSURBVDhPpVLbSqJRGPVqruY9Zp7gf4F5jbkSEhFE
00pFS4IyFSVTxHN4yAIPlCc8lGCpReJFqKAoaKJzNVeSMIgya/begw5SNAxzsfbP/vjWWt+3/s0D8F94
s/geQqEQTk9P4Xa7Ybfbv/Kq1SooyuUySqUSisUiCoUCstksUqkULi8vEY1GcXFxgWAwyHrn8znG4zEs
FkuLV6lUsFgsMJvN3sXLywt8Pt+3yWSCTqeD4+PjgdFoFPHu7u5YYTQa4eHhAcPhELQ2GAxwfX2NXq+H
dDrNemKxGFqtFpvIYDB8oSuxvW5ubnB/f49MJsPd3t4iHo9zlHx2dsbRNTweD0fJVquVC4fDbP+Dg4PP
K4FcLodGo8Ey6Pf7LAPqTMntdnvlTATx9PRE3bG3t/dxJUBxdXWF6XSKfD4PkjSXTCapE0fHJWFxlHx0
dMR5vV5KJpTfvJUAadTUajV0u11QMnWORCJoNptUkDmTVVCv16FUKn++EqAIBAI0aTidTthsNpo0SNI4
PDzE/v4+tFotEokEtra23hb4GxQKxQe5XA6JRPJ9WWOH3+//RMZzORwOmjTMZjP0ej1Nmrmq1Wrs7OxA
JpORdvBEItF0TYCEpaHfk5MTqclkeiZk6bJhic3NTalYLH4mZKlAIPixrLODvGkNSZr9rsfHR5yfn7Ok
VSoVtre3QchwuVzsGZNe8Pn83prAEjqdjoW1u7v7agKhUCjd2NigZHL9U19r+neA9wvhROqXtIFlogAA
dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAIwSURBVDhPpZLPahpRGMVdddX3aJ9gXqCv0ZVgkAGJ
ozEqMUpAExUlE2UwziQhZtRANMRoxP+g+SeKi6BCgkJM0K66EoUiSk+5FyJYSwPt4jBwued3vu/cUQBQ
/I+WDt6TLMs4OjqCKIoQBOGr4u7uDkQ3Nzcol8solUrI5/PIZDJIpVK4uLhALBbD6ekpTk5O6N3pdIrB
YACe59uK29tbzGYzTCaTv2o0GuHw8PDbcDjE09MTdnd3e263m1VcX1/Tg36/j2q1itfXV5CzXq+HQqGA
breLq6sreicej6PdbtOJXC7Xl3kHxWIR9/f3SKfTTKVSwfn5OUPM4XCYIWtIksQQs8/nYyKRCN3fbrd/
ngOy2SyazSbt4Pn5mXZAkon58fFxnhwOh/Hw8EDSYbVaP84BRIlEAuPxGLlcDrIsM8lkkiQxZFye5xli
3tnZYQ4ODogZb745IBaLWer1OjqdDoiZJJ+dnaHVahEgTZYkCY1GAyaT6ecSgCgUCpGmsb+/D7/fT5qG
2+2Gw+HA1tYWbDYbLi8vsba29mfAezIajR/0ej1WV1e/LwCOj48/SZIUDAQCpGl4vV44nU7SNE3d2NjA
+vo6dDod3Z1l2fECQBRFC/nu7e1pPR7Pi9Pp1P6eznGcVqPRvLAsq11ZWfmxABAEwcLzPH2uWq2GaDRK
mzabzTAYDOA4DsFgkP7GgiBAqVR2FwBv2t7epmVtbm4uTaBWq7UqlYqY50+4BPgX/QLhROqXAnK3iwAA
AABJRU5ErkJggg==
</value>
</data>
@@ -416,52 +416,52 @@
<data name="toolStripButton1.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAKUSURBVDhPlVJRSFNRGL5PEVE91Us9RE9pPUV0iyiEBO0t
egihosFgazilTacobs6FW3M6dqebW04nl3mlNrPpIFT24KZzOS1EB8oEU/StJ2EQstXXOcebKBXVB4cL
37nf/3/f/x/uT3gu3YE2dAOPA6Uo8138JtP/DsXghUL0Uy/eZASUuc//f4Fn4jW8/eiB9MGJctdZyDQX
CoUQDAbR19cHr9f7hEulUqBnZmYG09PT0Et3US3ehvHdQ7xeEBBM2fF08Bau207jStsp9m+hUMDOzg4E
Qchys7OzKBaL2NvbY0c1eBWW2COEF9wQ4gZY31ejN9EGhViOy+Yzxd3dXaytraGzs/NzR0dHNZdMJhmx
vb2NdDqNra0tVPlLYIjch5h2wJd8AWWogriqwsjICLLZLCKRCIi4XE7FcfF4HHNzc4jFYnwikcDo6Chf
KZyDRqqAWrqHUtOJFBV3d3fzkiSx/FartUSWc9zExASWl5fZDDY2NjA1NYXceg78y+N44L150HloaAhL
S0u0O8xm80lZvo9oNIp8Po/JyUmQLvz4+DgCgQBP7TqdTp6K7XY739/fj9bW1oOtHCAcDhszmQxyuRyo
eHV1FYTDysoK6OpoZ1IQi4uLaGpq+i7LjkIURQwMDMDv96OnpwculwsOh4PmhcViobYxNjYGg8Hw+wJ/
AylwrL6+HjU1NV9kah8k16VXBD6fj06aZqZ50d7ezrq2tLSgsbERdXV1LLtGo8kz4U+QtRjp1+1267q6
ujaJWMcuDkGv1+tqa2s3tVqtTq1Wf5XpfXg8HiN5lmxd8/PzGB4eZpNubm5GQ0MDiBjEIHvG5P1DoVCs
y9KjsNlsbFgmk+kXB8S2TqVSQalUHlohx/0ABs7CKxizUAUAAAAASUVORK5CYII=
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAKXSURBVDhPlVJfSFNxFL5PEVE91Us9RE9lPUV0iyiEBOst
egihosFg67IpbTpFcXMu3NqmY3e663Z35+Qyr9Sd2XQQKntw07mcFqIDZYIp+taTMAjZ6ovfBQezQvrg
8IPz5zvnO+dHUf/Aa+k+9NHbeC7UoDZw+efR+LFQDV8qxb8O4n2ORa3v4v8TvBJv4sMXP6TPHtR5z+PQ
H41GEYlEEAqFwHHcCyqTyYDY3NwcZmdnYZQeQCfeg/njU7xbYhHJOPFy+C5uOc7ievcZJbdUKmFvbw8s
y+ap+fl5lMtlHBwcKKYZvgFb4hnkJR/YpAn2TzoMprqhEutwzXquvL+/j42NDfT29n5zuVw6Kp1OK47d
3V1ks1ns7OygIXgVpthjiFk3Auk3UEfroRMbMDY2hnw+j1gsBpfLVVfRm0wmsbCwgEQiQadSKYyPj9MP
2QtgpHpopUeosZzKkOL+/n5akiRFv91uv1ohmJqawurqqrKDra0tzMzMoLBZAP32JJ5wdyqdR0ZGsLKy
QrrDarWerhAQxONxFItFTE9PQ5IkenJyEoIg0GRcj8dDk2Kn00mHw2F0dXVVrlKBLMvmXC6HQqEAUry+
vg5ZlrG2tgZyOtJZEAQsLy+jvb3919F6BaIoYmhoCMFgEAMDA/B6vXC73UQvbDYbGRsTExMwmUx/JzgO
Vqv1REtLCxobG79XBcLh8BWe5/lAIEA2TTQTvejp6VG6dnZ2oq2tDc3NzYp2hmGKVQShUMhMXp/PZ+jr
69t2Op2GqgSKooxGo6GpqWlbr9cbtFrtj6qg3+83syyrnGtxcRGjo6PKpjs6OtDa2gqj0Qie55VvzHEc
VCrVZhXBIRwOh7Isi8XyxwQMwxg0Gg3UanXVCX8DBs7CK9cQ274AAAAASUVORK5CYII=
</value>
</data>
<data name="toolStripButton2.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAMVSURBVDhPfZNdTJtlGIZ7amI8wLCKoz+bHiCoyaJLmJkx
asZIhG3MDvkZ3T4YlJYfnZ10WGTU0dIWWqD8FChbhpYiW7HRjdE5NiSbMdEt2RZmXapzXXBaDMYzEjK9
LO+6ZCPL7uQ+efLc1/e8z/t+soEKNavtLVfTJ6no2aukW6uks0yBqzQdZ/Fa7EVrsRY+y6eaNFp2PoMA
/PWd+9G+0MHCbDvx807+/MbOndM2fj95mPmQhdsnmmkukAvAfOTLA8RnrcTGC+95bBe3/O9y81gBv/q2
Ee17hxtduUScW7je+iY/uzT85jfTlCm/mwCoSiZtW8UXw40bmTz4Kqa8VIL7NzBWv4ERw8sMVb0kam5t
JvbiDK76Ggg25PDxtjX9shV5JfWlyyNVzB3/iIm650XzZ7r1DFeso3+Pmq7dKlFr1Sj53LKbi869iXDK
wv6cp1IEoFdSbB4zZXPnbBuhxk2iebB8HT1lKjqKVdgKlaLWolnPj30fckSfTUPe0yUifF/dWsXoedcu
vvcZmBmoY8pj4OsuPSG3nhPt1Xzh0DHpquGrxjxM+anfJmP31GvIfLJfUoYOlSnudryR/u/qq1rZdtN2
+cqZOZif+p9lZ5o7GZXJfBXpKQOS+u9g01vMenUPeaZfx3SfjjM9VZzuruRkZyUTTone2tdoKZAfFoCB
ParoRPMWLgzVEDywkfH6Fxmry8Jfm8UxQxbDuky8+17AI2Xg0mYwZNzKqE2iszo7MZE8JOsqS/tnLmgi
aHyF0ernxLIe56btCtyGtzneXiOOJAC3Z+zETjVyc+IDfhmvJRrQcWNkHz8Na5nzlnDNU8gV1w4u2/O5
1JrLD5bEgxo2rewjCTjXlgiVEjm6g6M9Njwej/C04z0uWnI49IkZo9GIXq9nIVoqfN1nEhMJwK0zVuan
HcTCbQR8ncRiMSKRCH6/H4fDQTgcZmlpicqKcq4NNojwQ4AH/zhXfS5TU1PE43GWl5eFFxcXCQQC6DSv
i7Ef3Im4idWyWq3vm83m6P2xJUn6o6ioaFCj0TyRbElKJvsfSis1OE3GifsAAAAASUVORK5CYII=
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAMPSURBVDhPfZNtTNtVFIf71cT4AYMVR182/YCgJosuQaMx
amAkAhuzQ15Gtz8dlJYXnZ10UGTU0dIWWqC8tFBchpYiW7HRyegUh2QzJroluoB1qc51wWkxGL+RkOlj
enUKxHiS58vJ+f3uOefeK/Pr1GzFV61mSFIxcEhJv1ZJb5UCd2UmrvJtOMq2YSt9gDc1GXTsux9ZSvDL
Z57/5mIPKwvdJC+4+PkjB7fO2fnx7AmWI1ZunmmnvUQuDJZj7x0luWAjMVX6F5P7uRF8ieunSvg+UEx8
6EWu9RUQc+Wx1Pkc37o1/BC00JYtvy3z61QVM/bd4sRoyy5mjj2BuTCd8JGdTDbtZNz4GKO1j4qcR5uN
ozyLrwPNhJvzaS2+b1iWCp+kvnxlvJbF068z3fiQKH5bv4Mx3XaGD6rpO6ASuU6NknesB7jkOkRrcdrK
kfx70oTBoKR4etKcy62Pu4i0PCmKR6q3M1Cloqdchb1UKXIdmh18OfQabxlyaS68t0KI70S/VjFxwb2f
zwNG5v2NzHqNfNBnIOIxcKa7jnedembc9bzfUoi5KP3TTeJBY/bdw5IycrxKcbvn2czft15Vattte+Sp
mTlWlP6HdV+G5x9xQJeZ5pfUv4bbnmfBp9/E/LCeuSE95wdqOddfw9neGqZdEoMNT9FRIj8hDPwHVfHp
9jwujtYTPrqLqaZHmGzMIdiQwyljDmP6bHyHH8YrZeHWZjFq2s2EXaK3LpfWYnlE1leV8dti2EzY9DgT
dQ+KZf0fbXsUeIwvcLq7XowkDG7OO0h82ML16Vf5bqqBeEjPtfHDfDOmZdFXwVVvKV+593LFUcTlzgK+
sOaxNGZO7eNvg0+6iIcqiZ3cy8kBO16vVzDnfJlL1nyOv2HBZDJhMBhYiVcKlgJm0ZEwuHHexvKck0S0
i1Cgl0QiQSwWIxgM4nQ6iUajrK2tUaOr5upIsxBvMtj449xNBczOzpJMJllfXxesrq4SCoXQa54RbW/c
yYbX8G/YbLZXLBZL/E7bkiT9VFZWNqLRaO7aWvsnSis1OOIL1MMAAAAASUVORK5CYII=
</value>
</data>
<data name="toolStripButton3.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAICSURBVDhPY4CDmcasDEu1MxmW6ixgWKbTwrBYVx0svkqL
h2GZViHDMu15QPFWhmXqUmBxDLBUZzVQwX84Xqzzg2GJpifDMs3rKOJLtZ4ADZWA6oKCpVo2IEmBtZb/
/U7m/E+5XP8/92TF/3lzYj/UdOT/S73c8D/yXNl/1W1eUEO0+6A6oWCZzgT+NebHkq/Ufyu+1fO/5GbP
/0Mzc/4/6kz7f3VS5k+QGAyb7A49wbBE5xpUJxQs1RUsvtVdB1PUcaj+//XSiP/7W+3/Xi/0+tyzrRBu
QPGtrgsg9VCdCFB8s3suTFHlpY5l5wtDDl5Odvt2MUjt/7YKp73FFzs+guQKbva8g2pBBTAXFN3sKQHx
X7RGiD8u8/x/JUPrz5NsG7XCm736QNs/F93qOQ/WgA6K7/QYF9/oOg7lMjwu9vR8VOrxH4xLvDxAYkBX
lgItagErwAaK73SLQZkMTyq8smAGPKn0zgSJ1d+v58i9NZEPrIAQeFTh2fek3Os/CD8u9+qFChMPgP7f
CPdCqccGqDBx4P/+eo4XXVGn3/TE/Qfhl52Rp0BiUGns4N+Bfs2/B3r7/h/oPXP/QO9vIP0fGUPFzoDV
7O/RgGoDatw2kR0oOP9Mb/0/dE24MEjt3wM980B6Gf7u7+3FpogYDNILdHqfLdC05UCBVaRgkJ5/B/ps
AT99qmQqX2rFAAAAAElFTkSuQmCC
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIHSURBVDhPY2CAgZnGrAxLtTMZluosYFim08KwWFcdLL5K
i4dhmVYhwzLteQzLdFoZlqlLwfWggKU6qxmW6fyH48U6PxiWaHoyLNO8jiK+VOsJwyotCTTNWjYgSYG1
lv/9Tub8T7lc/z/3ZMX/eXNiP9S05/9LvdzwP/Jc2X/VbV5QQ7T7UA1YpjOBf435seQr9d+Kb/X8L7nZ
8//QzJz/jzrT/l+dlPkTJAbDJrtDTzAs0bmGasBSXcHiW911MEUdh+r/Xy+N+L+/1f7v9UKvzz3bCuEG
FN/qugBSj2oAAwND8c3uuTBFlZc6lp0vDDl4Odnt28Ugtf/bKpz2Fl/s+AiSK7jZ8w5dLxjAXFB0s6cE
xH/RGiH+uMzz/5UMrT9Psm3UCm/26hff6vpcdKvnPLpeMCi+02NcfKPrOIz/uNjT81Gpx38wLvHyAKu5
2V1afKu7BUUjMii+0y0GYz+p8MqCGfCk0jsTJFZ/v54j99ZEPhRNuMCjCs++J+Ve/0H4cblXL7o8QfC4
zHMj3AulHhvQ5fGC//vrOV50RZ1+0xP3H4RfdkaeAomhq0MB/w70a/490Nv3/0DvmfsHen//P9D7HxlD
xc6A1ezv0UBo3DaR/e+B3vmne+r/oWvChUFq/x7omQfSy/B3f28vugJiMUgvw78DfbZ/D/Qs/3+gdxUp
GKQHpBcAOYOqX5UHGvcAAAAASUVORK5CYII=
</value>
</data>
<metadata name="$this.TrayHeight" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">