I have a .NET Core 3.1 Windows application (WinForm app) running on Windows 10 Version 10.0.17134 Build 17134. I am trying to connect and pair with Bluetooth devices using BluetoothLEAdvertisementWatcher. In the main form of the app I am using CefSharp.WinForms to display an HTML page.
My problem is that when I initialize the browser using _browser = new ChromiumWebBrowser(Settings.Default.IndexPageUrl); I can no longer pair with the Bluetooth device. I get AccessDenied returned from pairRequest.Status in my custom pairing event.
Here is the code:
using CefSharp;
using CefSharp.WinForms;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Concurrent;
using System.Drawing;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Windows.Devices.Bluetooth;
using Windows.Devices.Bluetooth.Advertisement;
using Windows.Devices.Enumeration;
namespace myApp
{
public partial class MainForm : Form
{
private readonly ILogger<MainForm> _logger;
private ChromiumWebBrowser _browser;
private static BluetoothLEAdvertisementWatcher _bleAdvertisementWatcher = null;
private static volatile ConcurrentDictionary<ulong,BleDeviceInfo> _bleDevicesInfo;
private readonly SemaphoreSlim _semaphoreSlim;
/// <summary>
/// Used to create a thread-safe boolean; do not access directly use AddingDevices instead
/// </summary>
private int _addingDevicesBool = 0;
public event Action<BleDeviceInfo> OnBleDeviceDetected;
public MainForm(): this(Program.LogFactory.CreateLogger<MainForm>())
{ }
public MainForm(ILogger<MainForm> logger)
{
_logger = logger;
_semaphoreSlim = new SemaphoreSlim(1, 1);
_bleDevicesInfo = new ConcurrentDictionary<ulong,BleDeviceInfo>();
InitBluetoothUtils();
InitializeBrowser();
}
#region browser setup
private void InitializeBrowser()
{
try
{
Cef.EnableHighDPISupport();
//When I comment out the 2 lines below I can succesfully pair with a BLE device
_browser = new ChromiumWebBrowser(Settings.Default.IndexPageUrl);
toolStripContainer.ContentPanel.Controls.Add(_browser);
//TODO: Uncomment the lines below to see Chrome Dev Tools
//#if DEBUG
// _browser.IsBrowserInitializedChanged += OnIsBrowserInitializedChanged;
//#endif
}
catch (Exception ex)
{
_logger.LogError(ex, Settings.BrowserInitError);
throw;
}
}
private void OnIsBrowserInitializedChanged(object sender, EventArgs e)
{
_browser.ShowDevTools();
}
#endregion
#region BLE setup
#region properties
/// <summary>
/// Thread-safe boolean property
/// </summary>
public bool ListeningForBleDevices
{
get { return (Interlocked.CompareExchange(ref _addingDevicesBool, 1, 1) == 1); }
set
{
if (value)
{
Interlocked.CompareExchange(ref _addingDevicesBool, 1, 0);
}
else
{
Interlocked.CompareExchange(ref _addingDevicesBool, 0, 1);
}
}
}
#endregion
private void InitBluetoothUtils()
{
StartListeningForBleDevices();
}
public void StartListeningForBleDevices()
{
ListeningForBleDevices = true;
StartBleWatcher();
}
private void StartBleWatcher()
{
try
{
ListeningForBleDevices = false;
if(_bleAdvertisementWatcher == null)
{
_bleAdvertisementWatcher = new BluetoothLEAdvertisementWatcher
{
ScanningMode = BluetoothLEScanningMode.Active
};
// Only activate the watcher when we're recieving values >= -80
_bleAdvertisementWatcher.SignalStrengthFilter.InRangeThresholdInDBm = -80;
// Stop watching if the value drops below -90 (user walked away)
_bleAdvertisementWatcher.SignalStrengthFilter.OutOfRangeThresholdInDBm = -90;
// Register events
_bleAdvertisementWatcher.Received += OnBleAdvWatcherReceived;
_bleAdvertisementWatcher.Stopped += OnBleAdvWatcherStopped;
// Wait 5 seconds to make sure the device is really out of range
_bleAdvertisementWatcher.SignalStrengthFilter.OutOfRangeTimeout = TimeSpan.FromMilliseconds(5000);
_bleAdvertisementWatcher.SignalStrengthFilter.SamplingInterval = TimeSpan.FromMilliseconds(2000);
}
//Start Watcher
ListeningForBleDevices = true;
_bleAdvertisementWatcher.Start();
}
catch (Exception ex)
{
_logger.LogError(ex, string.Empty);
throw;
}
}
public void StopBleWatcher()
{
_bleAdvertisementWatcher?.Stop();
ListeningForBleDevices = false;
}
private void OnBleAdvWatcherStopped(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementWatcherStoppedEventArgs args)
{
_logger.LogInformation("Ble Advertisement Watcher stopped");
}
/// <summary>
/// This event will fire multiple times for the same BLE device
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
private async void OnBleAdvWatcherReceived(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args)
{
if(!ListeningForBleDevices)
{
return;
}
BleDeviceInfo bleDeviceInfo = null;
try
{
var advLocalName = args.Advertisement.LocalName;
if (string.IsNullOrEmpty(advLocalName) || !advLocalName.Contains("my custom prefix", StringComparison.OrdinalIgnoreCase))
{
return;
}
_semaphoreSlim.Wait();
bleDeviceInfo = await SetBleDeviceInfo(args).ConfigureAwait(false);
_semaphoreSlim.Release();
}
catch (Exception ex)
{
_logger.LogError(ex, string.Empty);
_semaphoreSlim.Release();
throw;
}
if(bleDeviceInfo != null)
{
OnBleDeviceDetected?.Invoke(bleDeviceInfo);
}
}
private async Task<BleDeviceInfo> SetBleDeviceInfo(BluetoothLEAdvertisementReceivedEventArgs args)
{
BleDeviceInfo bleDeviceInfo = null;
try
{
var bluetoothAddress = args.BluetoothAddress;
var deviceExists = GetBleDevice(bluetoothAddress);
if(deviceExists == null)
{
BluetoothLEDevice bleDevice = await BluetoothLEDevice.FromBluetoothAddressAsync(bluetoothAddress);
//TODO: Pairing will not work when ChromiumWebBrowser gets initialized
var isPaired = await PairDevice(bleDevice).ConfigureAwait(false);
bleDeviceInfo = new BleDeviceInfo(bleDevice, args);
_bleDevicesInfo.AddOrUpdate(bleDeviceInfo.BluetoothAddress, bleDeviceInfo, (key, bleDeviceInfo) => bleDeviceInfo);
}
}
catch (Exception ex)
{
_logger.LogError(ex, string.Empty);
throw;
}
return bleDeviceInfo;
}
public BleDeviceInfo GetBleDevice(ulong bluetoothAddress)
{
try
{
return _bleDevicesInfo.Values.FirstOrDefault(d => d.BluetoothAddress.Equals(bluetoothAddress));
}
catch (Exception ex)
{
_logger.LogError(ex, string.Empty);
throw;
}
}
public async Task<bool> PairDevice(BluetoothLEDevice bleDevice)
{
if (bleDevice == null)
{
return false;
}
var isPaired = false; // bleDevice.DeviceInformation.Pairing.IsPaired;
try
{
var canPair = bleDevice.DeviceInformation.Pairing.CanPair;
var deviceId = bleDevice.DeviceId;
if (canPair)
{
DeviceInformationCustomPairing customPairing = bleDevice.DeviceInformation.Pairing.Custom;
customPairing.PairingRequested += OnCustomPairingRequested;
var pairRequest = await customPairing.PairAsync(DevicePairingKinds.ProvidePin, DevicePairingProtectionLevel.None);
customPairing.PairingRequested -= OnCustomPairingRequested;
var resultStatus = pairRequest.Status;
isPaired = resultStatus == DevicePairingResultStatus.Paired;
if (isPaired)
{
//TODO: Verify this code block
bleDevice.Dispose();
Thread.Sleep(2000); //try 2 second delay.
//Reload device so that the GATT services are there. This is why we wait.
bleDevice = await BluetoothLEDevice.FromIdAsync(deviceId);
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, string.Empty);
throw;
}
return isPaired;
}
public async Task<bool> PairDevice(ulong bluetoothAddress)
{
BluetoothLEDevice bleDevice = await BluetoothLEDevice.FromBluetoothAddressAsync(bluetoothAddress, BluetoothAddressType.Public);
return await PairDevice(bleDevice).ConfigureAwait(false);
}
private void OnCustomPairingRequested(DeviceInformationCustomPairing sender, DevicePairingRequestedEventArgs args)
{
try
{
//This method does not get called when ChromiumWebBrowser is initialized
//However, when _browser = new ChromiumWebBrowser is not called this method works as expected
var deviceName = args.DeviceInformation.Name;
var devicePin = GetBlePin(deviceName);
var pinDeferral = args.GetDeferral();
args.Accept(devicePin.ToString(CultureInfo.InvariantCulture));
pinDeferral.Complete();
}
catch (Exception)
{
throw;
}
}
private int GetBlePin(string serialNumber)
{
return 12345;
}
#endregion
}
}
Things I have tried:
Calling InitializeBrowser on a separate thread using Task.Run(() => { InitializeBrowser(); }).ConfigureAwait(false);
Calling InitBluetoothUtils on a separate thread using Task.Run(() => { InitBluetoothUtils(); }).ConfigureAwait(false);
Calling both InitializeBrowser and InitBluetoothUtils on separate threads
Tried using SynchronizationContext by getting the current context in the MainForm constructor, and then calling PairDevice(bleDevice) using that context
Each time I get AccessDenied. The only thing that works successfully is when I comment out _browser = new ChromiumWebBrowser(BASettings.Default.IndexPageUrl); and toolStripContainer.ContentPanel.Controls.Add(_browser);
Related
I have a WPF (.NET Framework 4.6) application that uses websocket-sharp (version 3.0.0) to create a websocket server.
I have a WebsocketServer and using EventHandler to tranfer event to MainWindow.xaml.cs but it not working. The MainWindow.xaml.cs listened to a RaiseOnScanDevice event but not any event invoked here.
I think this issue is relative to different thread. I try using Dispatcher.Invoke but it still not working.
System.Windows.Application.Current.Dispatcher.Invoke(new System.Action(() =>
{
RaiseOnScanDevice(this, new EventArgs());
}));
I found an issue (https://github.com/sta/websocket-sharp/issues/350) but the answers do not resolve my issue.
Please help me a solution for this issue.
WebsocketServer.cs file
public class WebsocketServer : WebSocketBehavior
{
private static readonly Lazy<WebsocketServer> lazyInstance = new Lazy<WebsocketServer>(() => new WebsocketServer());
public static WebsocketServer Instance
{
get
{
return lazyInstance.Value;
}
}
private const string TAG = "WebsocketServer";
private const string HOST_IP_ADDRESS = "127.0.0.2"; // localhost
private const int PORT = 38001;
public WebSocketServer socket;
private PacketHandler packetHandler = new PacketHandler();
public event EventHandler<EventArgs> RaiseOnScanDevice = new EventHandler<EventArgs>((a, e) => { });
public WebsocketServer()
{
Initialize();
}
public void Initialize()
{
socket = new WebSocketServer(IPAddress.Parse(HOST_IP_ADDRESS), PORT);
socket.AddWebSocketService<WebsocketServer>("/");
StartServer();
}
public void StartServer()
{
socket.Start();
}
public void StopServer()
{
socket.Stop();
}
protected override Task OnOpen()
{
return base.OnOpen();
}
protected override Task OnClose(CloseEventArgs e)
{
return base.OnClose(e);
}
protected override Task OnError(ErrorEventArgs e)
{
return base.OnError(e);
}
protected override Task OnMessage(MessageEventArgs e)
{
System.IO.StreamReader reader = new System.IO.StreamReader(e.Data);
string message = reader.ReadToEnd();
//Converting the event back to 'eventName' and 'JsonPayload'
PacketModel packet = packetHandler.OpenPacket(message);
HandleMessageFromClient(packet);
return base.OnMessage(e);
}
private void HandleMessageFromClient(PacketModel packet) {
var eventName = packet.EventName;
var data = packet.Data;
if (eventName == null || eventName.Equals(""))
{
return;
}
switch (eventName)
{
case SocketEvent.Hello:
Send("OK");
break;
case SocketEvent.ScanDevice:
ScanDevice();
break;
default:
break;
}
}
private void ScanDevice()
{
try
{
RaiseOnScanDevice(this, new EventArgs());
// or dispatch to Main Thread
System.Windows.Application.Current.Dispatcher.Invoke(new System.Action(() =>
{
RaiseOnScanDevice(this, new EventArgs());
}));
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
}
}
MainWindow.xaml.cs file
public partial class MainWindow : Window
{
public WebsocketServer WebsocketConnection
{
get { return WebsocketServer.Instance; }
}
public MainWindow()
{
InitializeComponent();
WebsocketConnection.RaiseOnScanDevice += SocketConnection_RaiseOnScanDevice;
}
private void SocketConnection_RaiseOnScanDevice(object sender, EventArgs e)
{
Console.WriteLine("SocketConnection_RaiseOnScanDevice");
}
The queue of messages is a good idea but you may want to use a lock to guard access to it. Most likely it won't be an issue but if you don't, you leave yourself open to the possibility of an error if the coroutine is reading from the queue as the websocket is writing to it. For example you could do something like this:
var queueLock = new object();
var queue = new Queue<MyMessageType>();
// use this to read from the queue
MyMessageType GetNextMessage()
{
lock (queueLock) {
if (queue.Count > 0) return queue.Dequeue();
else return null;
}
}
// use this to write to the queue
void QueueMessage(MyMessageType msg)
{
lock(queueLock) {
queue.Enqueue(msg);
}
}
I have found some code HERE and modified the same to call a phone number using skype, play an audio file and then disconnect. However, there are two issues in this code.
Audio file which is being played can be heard on the local system
but NOT in the phone call (the person receiving the call is not able to hear the audio played).
The call is not getting disconnected after audio file finishes
playing.
using Microsoft.Lync.Model;
using Microsoft.Lync.Model.Conversation;
using Microsoft.Lync.Model.Conversation.AudioVideo;
using Microsoft.Lync.Model.Device;
using Microsoft.Lync.Model.Extensibility;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows.Forms;
namespace LyncTest
{
public partial class frmCaller : Form
{
public frmCaller()
{
InitializeComponent();
}
private void btnCall_Click(object sender, EventArgs e)
{
//if this client is in UISuppressionMode...
if (client.InSuppressedMode && client.State == ClientState.Uninitialized)
{
//...need to initialize it
try
{
client.BeginInitialize(this.ClientInitialized, null);
}
catch (LyncClientException lyncClientException)
{
Console.WriteLine(lyncClientException);
}
catch (SystemException systemException)
{
if (LyncModelExceptionHelper.IsLyncException(systemException))
{
// Log the exception thrown by the Lync Model API.
Console.WriteLine("Error: " + systemException);
}
else
{
// Rethrow the SystemException which did not come from the Lync Model API.
throw;
}
}
}
else //not in UI Suppression, so the client was already initialized
{
//sign-in or contact selection
SignInToLync();
}
SendLyncCall("+6512345678", "Hello, I am calling regarding a pending change request");
}
LyncClient client = LyncClient.GetClient();
private void SignInToLync()
{
try
{
client.BeginSignIn("abc#contoso.com", "abc#contoso.com", "Pass#word99", HandleEndSignIn, null);
}
catch (LyncClientException lyncClientException)
{
Console.WriteLine(lyncClientException);
}
catch (SystemException systemException)
{
if (LyncModelExceptionHelper.IsLyncException(systemException))
{
// Log the exception thrown by the Lync Model API.
Console.WriteLine("Error: " + systemException);
}
else
{
// Rethrow the SystemException which did not come from the Lync Model API.
throw;
}
}
}
Automation _automation = LyncClient.GetAutomation();
ConversationWindow globalConv = null;
public void SendLyncCall(string numberToCall, string textToSpeech)
{
var targetContactUris = new List<string> { numberToCall }; //"tel:+4900000000"
_automation.BeginStartConversation(AutomationModalities.Audio, targetContactUris, null, StartConversationCallback, null);
while (this.globalConv == null)
{
Thread.Sleep(1);
}
if (globalConv != null)
{
//client.DeviceManager.EndPlayAudioFile(
// client.DeviceManager.BeginPlayAudioFile(#"C:\Temp\voice.wav", AudioPlayBackModes.AlertAndCommunication, false, AudioPlayed, null)
// );
}
}
private void StartConversationCallback(IAsyncResult asyncop)
{
// this is called once the dialing completes..
if (asyncop.IsCompleted == true)
{
ConversationWindow newConversationWindow = _automation.EndStartConversation(asyncop);
globalConv = newConversationWindow;
AVModality avModality = newConversationWindow.Conversation.Modalities[ModalityTypes.AudioVideo] as AVModality;
avModality.ModalityStateChanged += ConversationModalityStateChangedCallback;
}
}
/// <summary>
/// Called when the client in done initializing.
/// </summary>
/// <param name="result"></param>
private void ClientInitialized(IAsyncResult result)
{
//registers for conversation related events
//these events will occur when new conversations are created (incoming/outgoing) and removed
//client.ConversationManager.ConversationAdded += ConversationManager_ConversationAdded;
//client.ConversationManager.ConversationRemoved += ConversationManager_ConversationRemoved;
}
private void ConversationModalityStateChangedCallback(object sender, ModalityStateChangedEventArgs e)
{
AVModality avModality = sender as AVModality;
if (avModality != null)
{
switch (e.NewState)
{
case ModalityState.Disconnected:
avModality.ModalityStateChanged -= ConversationModalityStateChangedCallback;
break;
case ModalityState.Connected:
avModality.ModalityStateChanged -= ConversationModalityStateChangedCallback;
//foreach (char c in "SOS")
//{
// avModality.AudioChannel.BeginSendDtmf(c.ToString(), null, null);
// System.Threading.Thread.Sleep(500);
//}
client.DeviceManager.EndPlayAudioFile(client.DeviceManager.BeginPlayAudioFile(#"C:\Temp\voice.wav",
AudioPlayBackModes.Communication, false, AudioPlayed, null));
break;
case ModalityState.Invalid:
break;
case ModalityState.Notified:
break;
}
}
}
private void AudioPlayed(IAsyncResult audioPlayed)
{
if(audioPlayed.IsCompleted == true)
{
client.ConversationManager.Conversations[0].End();
}
}
private void HandleEndSignIn(IAsyncResult ar)
{
try
{
client.EndSignIn(ar);
}
catch (Exception e)
{
Console.Out.WriteLine(e);
}
}
private void frmCaller_FormClosing(object sender, FormClosingEventArgs e)
{
GC.Collect();
}
}
}
Any help would be appreciated. Thank you.
It was confirmed by Microsoft that this is not possible using client side code. I need to use UCMA and develop a server side solution for the same.
First time posting so sorry for the formatting if it is incorrect.
I am having trouble filtering an observable collection based upon a date given. The application will have a calendar where the user can click a date and below will appear the appointments for that date.
There are two classes, one being the dataManager which will get the data from Azure, and the appointment page itself.
Here is the appointmentPage Class:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using XamForms.Controls;
namespace TodoAzure
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class AppointmentPage : ContentPage
{
TodoItemManager manager;
CalendarVM vm = new CalendarVM();
public AppointmentPage()
{
InitializeComponent();
manager = TodoItemManager.DefaultManager;
calendar.DateClicked += (sender, e) =>
{
System.Diagnostics.Debug.WriteLine(calendar.SelectedDates);
};
calendar.SetBinding(Calendar.DateCommandProperty, nameof(vm.DateChosen));
calendar.SetBinding(Calendar.SelectedDateProperty, nameof(vm.DateSelected));
calendar.BindingContext = vm;
}
protected override async void OnAppearing()
{
base.OnAppearing();
// Set syncItems to true in order to synchronize the data on startup when running in offline mode
await RefreshItems(true, syncItems: false);
}
//PULL TO REFRESH
public async void OnRefresh(object sender, EventArgs e)
{
var list = (ListView)sender;
Exception error = null;
try
{
await RefreshItems(false, true);
}
catch (Exception ex)
{
error = ex;
}
finally
{
list.EndRefresh();
}
if (error != null)
{
await DisplayAlert("Refresh Error", "Couldn't refresh data (" + error.Message + ")", "OK");
}
}
public async void OnSyncItems(object sender, EventArgs e)
{
await RefreshItems(true, true);
}
private async Task RefreshItems(bool showActivityIndicator, bool syncItems)
{
using (var scope = new ActivityIndicatorScope(syncIndicator, showActivityIndicator))
{
appointmentPage.ItemsSource = await manager.GetAppointmentItemsAsync(syncItems);
}
}
private class ActivityIndicatorScope : IDisposable
{
private bool showIndicator;
private ActivityIndicator indicator;
private Task indicatorDelay;
public ActivityIndicatorScope(ActivityIndicator indicator, bool showIndicator)
{
this.indicator = indicator;
this.showIndicator = showIndicator;
if (showIndicator)
{
indicatorDelay = Task.Delay(2000);
SetIndicatorActivity(true);
}
else
{
indicatorDelay = Task.FromResult(0);
}
}
private void SetIndicatorActivity(bool isActive)
{
this.indicator.IsVisible = isActive;
this.indicator.IsRunning = isActive;
}
public void Dispose()
{
if (showIndicator)
{
indicatorDelay.ContinueWith(t => SetIndicatorActivity(false), TaskScheduler.FromCurrentSynchronizationContext());
}
}
}
}
And here is the data manger class:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.WindowsAzure.MobileServices;
using Microsoft.WindowsAzure.MobileServices.Sync;
#if OFFLINE_SYNC_ENABLED
using Microsoft.WindowsAzure.MobileServices.SQLiteStore;
using Microsoft.WindowsAzure.MobileServices.Sync;
#endif
namespace TodoAzure
{
public partial class TodoItemManager
{
static TodoItemManager defaultInstance = new TodoItemManager ();
MobileServiceClient client;
IMobileServiceTable<TodoItem> todoTable;
IMobileServiceTable<AppointmentItem> appointmentTable;
private TodoItemManager ()
{
this.client = new MobileServiceClient (
Constants.ApplicationURL);
this.todoTable = client.GetTable<TodoItem> ();
this.appointmentTable = client.GetTable<AppointmentItem>();
}
public static TodoItemManager DefaultManager
{
get { return defaultInstance; }
private set { defaultInstance = value; }
}
public MobileServiceClient CurrentClient
{
get { return client; }
}
public bool IsOfflineEnabled
{
get { return appointmentTable is Microsoft.WindowsAzure.MobileServices.Sync.IMobileServiceSyncTable<AppointmentItem>; }
}
// INSERT AND UPDATE METHODS
public async Task SaveTaskAsync (TodoItem item)
{
if (item.Id == null)
await todoTable.InsertAsync (item);
else
await todoTable.UpdateAsync (item);
}
public async Task SaveTaskAsync(AppointmentItem appointment)
{
if (appointment.Id == null)
await appointmentTable.InsertAsync(appointment);
else
await appointmentTable.UpdateAsync(appointment);
}
public async Task<ObservableCollection<AppointmentItem>> GetAppointmentItemsAsync(bool syncItems = false)
{
try
{
IEnumerable<AppointmentItem> items = await appointmentTable
.ToEnumerableAsync();
return new ObservableCollection<AppointmentItem>(items);
}
catch (MobileServiceInvalidOperationException msioe)
{
Debug.WriteLine(#"Invalid sync operation: {0}", msioe.Message);
}
catch (Exception e)
{
Debug.WriteLine(#"Sync error: {0}", e.Message);
}
return null;
}
}
Any Help will be greatly appreciated.
to filter an IEnumerable by Date, try this
// items is ObservableCollection<AppointmentItem>
var filtered = items.Where(x => x.Date == SelectedDate);
Am creating a outlook add-in to track mail processing from mailbox. Am wrapping the folders and the items(adding some events into it ) and storing them in a local list to avoid GC clearing all the events after first execution. However still the folder add event only fires once. Not sure what is the problem.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using OutlookNS = Microsoft.Office.Interop.Outlook;
using Office = Microsoft.Office.Core;
using System.Net;
using System.Windows.Forms;
namespace OutlookAuditor
{
public partial class ThisAddIn
{
#region private variables
OutlookNS._NameSpace outNS;
OutlookNS.Explorer explorer;
string profileName = string.Empty;
List<SuperMailFolder> wrappedFolders = new List<SuperMailFolder>();
Logger logger = new Logger();
SuperMailFolder folderToWrap;
#endregion
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
try
{
OutlookNS.Application application = this.Application;
//Get the MAPI namespace
outNS = application.GetNamespace("MAPI");
//Get UserName
string profileName = outNS.CurrentUser.Name;
//Create a new outlook application
//I had to do this because my systems default mail box was ost file other just the below commented line is enough
//OutlookNS.MAPIFolder inbox = outNS.GetDefaultFolder(OutlookNS.OlDefaultFolders.olFolderInbox) as OutlookNS.MAPIFolder;
OutlookNS.MAPIFolder inbox;
OutlookNS.Folders folders = outNS.Folders;
OutlookNS.MAPIFolder selectedFolder = null;
if (folders.Count > 1)
{
List<string> folderNames = new List<string>();
foreach (OutlookNS.Folder folder in folders)
{
folderNames.Add(folder.Name);
}
using (selectMailBox frmSelect = new selectMailBox(folderNames))
{
if (DialogResult.OK == frmSelect.ShowDialog())
{
selectedFolder = folders[frmSelect.SelectedFolder];
}
}
}
else
{
selectedFolder = folders[1];
}
logger.SaveLog("Folder Selected " + selectedFolder.Name);
inbox = selectedFolder.Folders["Inbox"];//as OutlookNS.MAPIFolder;
//Create a super mail folder
folderToWrap = new SuperMailFolder(inbox, profileName);
wrappedFolders.Add(folderToWrap);
wrappedFolders.AddRange(folderToWrap.wrappedSubFolders);
//System.Runtime.InteropServices.Marshal.ReleaseComObject(inbox);
}
catch (Exception ex)
{
logger.WriteException(ex);
}
finally
{
}
}
private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
{
}
#region VSTO generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InternalStartup()
{
this.Startup += new System.EventHandler(ThisAddIn_Startup);
this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
}
#endregion
}
#region SuperMailItem object
class SuperMailItem
{
//local variable for avoiding GC invocation
OutlookNS.MailItem item;
string _profileName;
OutlookAuditor.Common.AuditItem auditItem;
string parentMailID;
string _folderName = string.Empty;
OutlookNS.MailItem replyItem;
Logger logger = new Logger();
//constructor that wraps mail item with required events
internal SuperMailItem(OutlookNS.MailItem MailItemToWrap, string profileName,string folderName)
{
try
{
item = MailItemToWrap as OutlookNS.MailItem;
_folderName = folderName;
if (item is OutlookNS.MailItem)
{
logger.SaveLog(item.Subject);
item.PropertyChange += MailItemToWrap_PropertyChange;
//item.PropertyChange += new OutlookNS.ItemEvents_10_PropertyChangeEventHandler(MailItemToWrap_PropertyChange);
((OutlookNS.ItemEvents_10_Event)item).Reply += SuperMailItem_Reply;
}
}
catch(Exception ex)
{
logger.WriteException(ex,"SuperMailItem Constructor");
}
}
void SuperMailItem_Reply(object Response, ref bool Cancel)
{
try
{
parentMailID = string.Empty;
replyItem = Response as OutlookNS.MailItem;
((OutlookNS.ItemEvents_10_Event)replyItem).Send += SuperMailItem_Send;
}
catch(Exception ex)
{
logger.WriteException(ex);
}
}
//this event is not firing
void SuperMailItem_Send(ref bool Cancel)
{
try
{
if (!Cancel)
{
createAuditItem();
auditItem.ActionDescription = "REPLY_SENT";
SaveLog();
}
}
catch(Exception ex)
{
logger.WriteException(ex);
}
}
//property change event- fires when any property of a mail item changes
void MailItemToWrap_PropertyChange(string Name)
{
try
{
createAuditItem();
//We are interested in UnRead property, if this property changes audit.
if (Name == "UnRead")
{
if (!item.UnRead)
{
auditItem.ActionDescription = "MAIL_READ";
}
else
{
auditItem.ActionDescription = "MAIL_UNREAD";
}
}
SaveLog();
}
catch(Exception ex)
{
logger.WriteException(ex);
}
}
void createAuditItem()
{
auditItem = new Common.AuditItem();
auditItem.ActionTimestamp = DateTime.Now;
auditItem.EntryID = item.EntryID;
auditItem.ProfileName = _profileName;
auditItem.ReceivedTimestamp = item.ReceivedTime;
auditItem.SystemIP = Helper.SystemIP();
auditItem.UserName = Helper.UserID();
auditItem.OriginalMailSentBy = item.Sender.Name;
auditItem.FolderName = _folderName;
auditItem.Subject = item.Subject;
}
void SaveLog()
{
Logger logger = new Logger();
logger.Save(auditItem);
}
}
#endregion
#region SuperMailFolder object
class SuperMailFolder
{
#region private variables
OutlookNS.MAPIFolder _wrappedFolder;
string _profileName;
List<SuperMailItem> wrappedItems = new List<SuperMailItem>();
public List<SuperMailFolder> wrappedSubFolders = new List<SuperMailFolder>();
string folderName = string.Empty;
Logger logger = new Logger();
#endregion
#region constructor
internal SuperMailFolder(OutlookNS.MAPIFolder folder, string profileName)
{
try
{
//assign it to local private master
_wrappedFolder = folder;
folderName = folder.Name;
_profileName = profileName;
//assign event handlers for the folder
_wrappedFolder.Items.ItemAdd +=Items_ItemAdd;
_wrappedFolder.Items.ItemRemove += Items_ItemRemove;
refreshItemList();
//Go through all the subfolders and wrap them as well
foreach (OutlookNS.MAPIFolder tmpFolder in _wrappedFolder.Folders)
{
logger.SaveLog("Wrapping folder " + tmpFolder.Name);
SuperMailFolder tmpWrapFolder = new SuperMailFolder(tmpFolder, _profileName);
wrappedSubFolders.Add(tmpWrapFolder);
wrappedSubFolders.AddRange(tmpWrapFolder.wrappedSubFolders);
}
}
catch(Exception ex)
{
logger.WriteException(ex);
}
}
#endregion
void Items_ItemRemove()
{
refreshItemList();
}
#region Handler of addition item into a folder
void Items_ItemAdd(object Item)
{
try
{
if (Item is OutlookNS.MailItem)
{
OutlookNS.MailItem item = Item as OutlookNS.MailItem;
wrappedItems.Add(new SuperMailItem(item, _profileName, folderName));
logger.SaveLog("Adding new item. New collection count:" + wrappedItems.Count.ToString());
OutlookAuditor.Common.AuditItem auditItem = new Common.AuditItem();
auditItem.ActionTimestamp = DateTime.Now;
auditItem.EntryID = item.EntryID;
auditItem.ProfileName = _profileName;
auditItem.ReceivedTimestamp = item.ReceivedTime;
auditItem.SystemIP = Helper.SystemIP();
auditItem.UserName = Helper.UserID();
auditItem.ActionDescription = "FOLDER_ADD";
auditItem.FolderName = folderName;
auditItem.OriginalMailSentBy = item.Sender.Name;
auditItem.Subject = item.Subject;
logger.Save(auditItem);
}
}
catch(Exception ex)
{
logger.WriteException(ex);
}
}
void refreshItemList()
{
try
{
wrappedItems.Clear();
wrappedItems = new List<SuperMailItem>();
logger.SaveLog("Wrapping items in " + folderName);
//Go through all the items and wrap it.
foreach (OutlookNS.MailItem item in _wrappedFolder.Items)
{
try
{
if (item is OutlookNS.MailItem)
{
OutlookNS.MailItem mailItem = item as OutlookNS.MailItem;
SuperMailItem wrappedItem = new SuperMailItem(mailItem, _profileName, folderName);
wrappedItems.Add(wrappedItem);
}
}
catch (Exception ex)
{
logger.WriteException(ex);
}
}
logger.SaveLog("Wrapped items in " + folderName + ":" + wrappedItems.Count.ToString());
}
catch(Exception ex)
{
logger.WriteException(ex);
}
}
#endregion
}
#endregion
static class Helper
{
public static string SystemIP()
{
string hostName = Dns.GetHostName();
string hostAddress = Dns.GetHostByName(hostName).AddressList[0].ToString();
return hostAddress;
}
public static string UserID()
{
return System.Security.Principal.WindowsIdentity.GetCurrent().Name;
}
}
}
The following code is the problem:
_wrappedFolder.Items.ItemAdd +=Items_ItemAdd;
_wrappedFolder.Items.ItemRemove += Items_ItemRemove;
The object that fires the events must be alive - in your case you set up an event handler on an implicit variable returned from the _wrappedFolder.Items property - as soon as the GC releases that implicit variable, no events will fire. Declare the Items object on the class level to make sure it stays referenced and alive.
I've created a user control (windows form application) to get an instance of Word (either active or new), and provide a button to open documents into that instance using a file dialog picker.
The form contains 2 buttons, 1 for getting the word instance and another for opening a document. It also contains a list box for displaying the open documents, and an openfiledialog control to provide the means for selecting documents to open.
I am handling the Application.DocumentOpen event in order to populate the listbox...
m_wordApp.DocumentOpen += new msoWord.ApplicationEvents4_DocumentOpenEventHandler(m_wordApp_DocumentOpen);
I am determining when i need to reinvoke my method that populates the listbox to ensure that access to the control is on the same thread that created it....
private void AddDocument(string name)
{
try
{
if (m_documentsListBox.InvokeRequired && m_documentsListBox.IsHandleCreated&&!m_documentsListBox.IsDisposed)
{
this.Invoke(m_AddDocument, new object[] { name });
return;
}
if (!m_documentsListBox.Items.Contains(name))
m_documentsListBox.Items.Add(name);
}
catch (Exception ex)
{
}
}
Im not using 2 dots, and i believe i am releasing any COM objects correctly.
Why does the application hang on either the line of code that opens the document ...
WordDoc = m_wordDocs.Open(ref fileName);
or the line that reinvokes the AddDocument() method...
this.Invoke(m_AddDocument, new object[] { name });
somewhere along the line i think i must be having a thread issue, because the hang only happens if i choose to open a document using the button, rather than from within the Word application directly.
full code below...
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
// use these for the core office & word references
using msoWord = Microsoft.Office.Interop.Word;
namespace MCDevNET.Office.Word.TestUIControls
{
public partial class OpenWordDocument : Form
{
public OpenWordDocument()
{
InitializeComponent();
m_openWordButton.Click += new EventHandler(buttonOpenWordApp_Click);
m_openDocumentButton.Click += new EventHandler(buttonOpenDocument_Click);
m_AddDocument = new UpdateListControl(AddDocument);
}
#region Form: control eventHandlers
void buttonOpenWordApp_Click(object sender, EventArgs e)
{
try
{
if (!IsValid(m_wordApp))
WordApp = GetInstance();
AddAllDocuments();
}
catch (Exception ex)
{
}
}
void buttonOpenDocument_Click(object sender, EventArgs e)
{
OpenWordDoc();
}
public delegate void UpdateListControl(string name);
private UpdateListControl m_AddDocument;
private void AddDocument(string name)
{
try
{
if (m_documentsListBox.InvokeRequired && m_documentsListBox.IsHandleCreated&&!m_documentsListBox.IsDisposed)
{
this.Invoke(m_AddDocument, new object[] { name });
return;
}
if (!m_documentsListBox.Items.Contains(name))
m_documentsListBox.Items.Add(name);
}
catch (Exception ex)
{
}
}
private void AddAllDocuments()
{
try
{
m_documentsListBox.Items.Clear();
if (m_wordDocs != null)
{
for (int i = 1; i <= m_wordDocs.Count; i++)
AddDocument(m_wordDocs[i].Name);
}
}
catch (Exception ex)
{
}
}
#endregion
#region Word: properties & eventhandlers
private msoWord.Document m_wordDoc;
public msoWord.Document WordDoc
{
get { return m_wordDoc; }
private set
{
try
{
if (m_wordDoc != value)
{
ReleaseCOMObject(m_wordDoc);
m_wordDoc = value;
}
}
catch (Exception ex)
{
}
}
}
private msoWord.Documents m_wordDocs;
public msoWord.Documents WordDocs
{
get { return m_wordDocs; }
private set
{
try
{
if (m_wordDocs != value)
{
ReleaseCOMObject(m_wordDocs);
m_wordDocs = value;
}
}
catch (Exception ex)
{
}
}
}
private msoWord.Application m_wordApp;
public msoWord.Application WordApp
{
get { return m_wordApp; }
set
{
try
{
if (m_wordApp != value)
{
if (m_wordApp != null)
{
m_wordApp.DocumentOpen -= new msoWord.ApplicationEvents4_DocumentOpenEventHandler(m_wordApp_DocumentOpen);
ReleaseCOMObject(m_wordApp);
}
m_wordApp = value;
if (IsValid(m_wordApp))
{
m_wordApp.DocumentOpen += new msoWord.ApplicationEvents4_DocumentOpenEventHandler(m_wordApp_DocumentOpen);
WordDocs = m_wordApp.Documents;
}
}
}
catch (Exception ex)
{
}
}
}
void m_wordApp_DocumentOpen(msoWord.Document doc)
{
try
{
string name = doc.Name;
AddDocument(name);
}
catch (Exception ex)
{
}
finally
{
ReleaseCOMObject(doc);
doc = null;
}
}
private msoWord.Application GetInstance()
{
msoWord.Application app = null;
try
{
app = (msoWord.Application)Marshal.GetActiveObject("Word.Application");
}
catch (Exception ex)
{
if (app == null)
app = new msoWord.Application();
}
finally
{
if (IsValid(app))
{
app.Visible = true;
app.Activate();
}
}
return app;
}
private void OpenWordDoc()
{
try
{
m_openFileDialog.AddExtension = true;
m_openFileDialog.Filter = "All Word (*.docx; *.docm; *.doc; *.dotx; *.dotm; *.dot)|*.docx;*.docm;*.doc;*.dotx;*.dotm;*.dot|Word Documents (*.docx)|*.docx|Word Macro-Enabled Documents (*.docm)|*.docm|Word 97-2003 Documents (*.doc)|*.doc|All Word Templates (*.dotx; *.dotm; *.dot)|*.dotx;*.dotm;*.dot|Word Templates (*.dotx)|*.dotx|Word Macro-Enabled Templates (*.dotm)|*.dotm)";
m_openFileDialog.FilterIndex = 1;
m_openFileDialog.Multiselect = false;
m_openFileDialog.Title = "Open Word Document";
if (m_openFileDialog.ShowDialog() == DialogResult.OK)
{
object fileName = m_openFileDialog.FileName;
WordDoc = m_wordDocs.Open(ref fileName);
}
}
catch (Exception ex)
{
}
}
private bool IsValid(msoWord.Application app)
{
try
{
if (app != null)
{
string name = app.Caption;
return true;
}
}
catch (Exception ex)
{
}
return false;
}
#endregion
private void ReleaseCOMObject(object comObject)
{
try
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
if (comObject != null && Marshal.IsComObject(comObject))
Marshal.ReleaseComObject(comObject);
}
catch (Exception ex)
{
}
}
}
}
namespace MCDevNET.Office.Word.TestUIControls
{
partial class OpenWordDocument
{
/// <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.m_documentsListBox = new System.Windows.Forms.ListBox();
this.m_openDocumentButton = new System.Windows.Forms.Button();
this.m_openWordButton = new System.Windows.Forms.Button();
this.m_openFileDialog = new System.Windows.Forms.OpenFileDialog();
this.SuspendLayout();
//
// lb_Documents
//
this.m_documentsListBox.FormattingEnabled = true;
this.m_documentsListBox.Location = new System.Drawing.Point(12, 41);
this.m_documentsListBox.Name = "lb_Documents";
this.m_documentsListBox.Size = new System.Drawing.Size(156, 134);
this.m_documentsListBox.TabIndex = 8;
//
// m_openDocumentButton
//
this.m_openDocumentButton.Location = new System.Drawing.Point(93, 12);
this.m_openDocumentButton.Name = "m_openDocumentButton";
this.m_openDocumentButton.Size = new System.Drawing.Size(75, 23);
this.m_openDocumentButton.TabIndex = 7;
this.m_openDocumentButton.Text = "Doc";
this.m_openDocumentButton.UseVisualStyleBackColor = true;
//
// m_openWordButton
//
this.m_openWordButton.Location = new System.Drawing.Point(12, 12);
this.m_openWordButton.Name = "m_openWordButton";
this.m_openWordButton.Size = new System.Drawing.Size(75, 23);
this.m_openWordButton.TabIndex = 6;
this.m_openWordButton.Text = "Word";
this.m_openWordButton.UseVisualStyleBackColor = true;
//
// m_openFileDialog
//
this.m_openFileDialog.FileName = "openFileDialog1";
//
// OpenWordDocument
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(182, 184);
this.Controls.Add(this.m_documentsListBox);
this.Controls.Add(this.m_openDocumentButton);
this.Controls.Add(this.m_openWordButton);
this.Name = "OpenWordDocument";
this.Text = "OpenWordDocument";
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.ListBox m_documentsListBox;
private System.Windows.Forms.Button m_openDocumentButton;
private System.Windows.Forms.Button m_openWordButton;
private System.Windows.Forms.OpenFileDialog m_openFileDialog;
}
}
The hang happens on the call to Documents.Open(fileName)
You have an event handler wired up for the Application.DocumentOpen event. On removing this event handler the hang no longer occurs.
I presume the reason for the problem is that you are getting deadlocked as Word tries to fire that event before the Documents.Open call returns. Meaning the thread that handles the event is still busy.
Replacing
WordDoc = m_wordDocs.Open(ref fileName)
with
new System.Threading.Tasks.Task(() => WordDoc = m_wordDocs.Open(ref fileName))
.Start();
To open the document on a different thread seems to resolve the issue.