I really need help with this one.
I have written an adding that adds a some text to the subject line of an email on send it also sets the same value in a property on the email through the PropertyAccessor. This works beautifully when the email is created through the Outlook interface.
The problem I have encountered is when a user is working with excel and decides to send the spreadsheet as an attachment from the Excel 2010 -> File -> Save & Send -> Send as Attachment Menu Item.
This will open the outlook 2010 mail editor as you would expect, the user can address the email and press send as normal and the email will be sent as normal. The problem is that the email editor does not close after sending as you would expect.
I am using the Globals.ThisAddIn.Application.ItemSend event to make my changes to the email. I notice also on debug that this event fires a couple of times when sending. The cancel argument of this event is always false during execution.
Finally found something that seems to work
Developing an Inspector Wrapper for Outlook 2010
In case the link goes dead as they are likely to do
Here's the wrapper
namespace OutlookAddIn
{
// Eventhandler used to correctly clean up resources.
// <param name="id">The unique id of the Inspector instance.</param>
internal delegate void InspectorWrapperClosedEventHandler(Guid id);
// The base class for all inspector wrappers.
internal abstract class InspectorWrapper
{
// Event notification for the InspectorWrapper.Closed event.
// This event is raised when an inspector has been closed.
// The unique ID that identifies an inspector window.
protected InspectorWrapper(Inspector inspector)
{
Id = Guid.NewGuid();
Inspector = inspector;
// Register Inspector events here
((InspectorEvents_10_Event) Inspector).Close += InspectorClose;
((InspectorEvents_10_Event) Inspector).Activate += Activate;
(Inspector).Deactivate += Deactivate;
(Inspector).BeforeMaximize += BeforeMaximize;
(Inspector).BeforeMinimize += BeforeMinimize;
(Inspector).BeforeMove += BeforeMove;
(Inspector).BeforeSize += BeforeSize;
(Inspector).PageChange += PageChange;
// Initialize is called to give the derived wrappers.
Initialize();
}
public Guid Id { get; private set; }
// The Outlook Inspector instance.
public Inspector Inspector { get; private set; }
public event InspectorWrapperClosedEventHandler Closed;
// .ctor
// <param name="inspector">The Outlook Inspector instance that should be handled.</param>
// Event handler for the Inspector Close event.
private void InspectorClose()
{
// Call the Close Method - the derived classes can implement cleanup code
// by overriding the Close method.
Close();
// Unregister Inspector events.
((InspectorEvents_10_Event) Inspector).Close -= InspectorClose;
// Clean up resources and do a GC.Collect().
Inspector = null;
GC.Collect();
GC.WaitForPendingFinalizers();
// Raise the Close event.
if (Closed != null)
{
Closed(Id);
}
}
protected virtual void Initialize()
{}
// Method is called when another page of the inspector has been selected.
// <param name="ActivePageName">The active page name by reference.</param>
protected virtual void PageChange(ref string ActivePageName)
{}
// Method is called before the inspector is resized.
// <param name="Cancel">To prevent resizing, set Cancel to true.</param>
protected virtual void BeforeSize(ref bool Cancel)
{}
// Method is called before the inspector is moved around.
// <param name="Cancel">To prevent moving, set Cancel to true.</param>
protected virtual void BeforeMove(ref bool Cancel)
{}
// Method is called before the inspector is minimized.
// <param name="Cancel">To prevent minimizing, set Cancel to true.</param>
protected virtual void BeforeMinimize(ref bool Cancel)
{}
// Method is called before the inspector is maximized.
// <param name="Cancel">To prevent maximizing, set Cancel to true.</param>
protected virtual void BeforeMaximize(ref bool Cancel)
{}
// Method is called when the inspector is deactivated.
protected virtual void Deactivate()
{}
// Method is called when the inspector is activated.
protected virtual void Activate()
{}
// Derived classes can do a cleanup by overriding this method.
protected virtual void Close()
{}
// This factory method returns a specific InspectorWrapper or null if not handled.
// <param name=”inspector”>The Outlook Inspector instance.</param>
// Returns the specific wrapper or null.
public static InspectorWrapper GetWrapperFor(Inspector inspector)
{
// Retrieve the message class by using late binding.
string messageClass = inspector.CurrentItem.GetType().InvokeMember("MessageClass", BindingFlags.GetProperty,
null, inspector.CurrentItem, null);
// Depending on the message class, you can instantiate a
// different wrapper explicitly for a given message class by
// using a switch statement.
switch (messageClass)
{
case "IPM.Contact":
return new ContactItemWrapper(inspector);
case "IPM.Journal":
return new ContactItemWrapper(inspector);
case "IPM.Note":
return new MailItemWrapper(inspector);
case "IPM.Post":
return new PostItemWrapper(inspector);
case "IPM.Task":
return new TaskItemWrapper(inspector);
}
// Or, check if the message class begins with a specific fragment.
if (messageClass.StartsWith("IPM.Contact.X4U"))
{
return new X4UContactItemWrapper(inspector);
}
// Or, check the interface type of the item.
if (inspector.CurrentItem is AppointmentItem)
{
return new AppointmentItemWrapper(inspector);
}
// No wrapper is found.
return null;
}
}
// Derive a wrapper for each message class/item type.
}
and here is an example override
internal class MailItemWrapper : InspectorWrapper
{
// The Object instance behind the Inspector, which is the current item.
public MailItemWrapper(Inspector inspector)
: base(inspector)
{
// Get the item in the current Inspector.
Item = (MailItem) Inspector.CurrentItem;
// Register Item events.
Item.Open += Item_Open;
Item.Write += Item_Write;
}
public MailItem Item { get; private set; }
// This method is called when the item is visible and the UI is initialized.
// <param name="Cancel">When you set this property to true, the Inspector is closed.</param>
private void Item_Open(ref bool Cancel)
{
// TODO: Implement something
}
// This method is called when the item is saved.
// <param name="Cancel">When set to true, the save operation is cancelled.</param>
private void Item_Write(ref bool Cancel)
{
//TODO: Implement something
}
// The Close method is called when the inspector has been closed.
// Do your cleanup tasks here.
// The UI is gone, cannot access it here.
protected override void Close()
{
// Unregister events.
Item.Write -= Item_Write;
Item.Open -= Item_Open;
// Release references to COM objects.
Item = null;
// Set item to null to keep a reference in memory of the garbage collector.
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
and here is the ThisAddin class
public partial class ThisAddIn
{
// Holds a reference to the Application.Inspectors collection.
// Required to get notifications for NewInspector events.
private Inspectors _inspectors;
// A dictionary that holds a reference to the inspectors handled by the add-in.
private Dictionary<Guid, InspectorWrapper> _wrappedInspectors;
private void ThisAddInStartup(object sender, EventArgs e)
{
_wrappedInspectors = new Dictionary<Guid, InspectorWrapper>();
_inspectors = Globals.ThisAddIn.Application.Inspectors;
_inspectors.NewInspector += WrapInspector;
// Also handle existing Inspectors
// (for example, double-clicking a .msg file).
foreach (Inspector inspector in _inspectors)
{
WrapInspector(inspector);
}
}
// Wrap an Inspector, if required, and store it in memory to get events of the wrapped Inspector.
// <param name="inspector">The Outlook Inspector instance.</param>
private void WrapInspector(Inspector inspector)
{
var wrapper = InspectorWrapper.GetWrapperFor(inspector);
if (wrapper != null)
{
// Register the Closed event.
wrapper.Closed += WrapperClosed;
// Remember the inspector in memory.
_wrappedInspectors[wrapper.Id] = wrapper;
}
}
// Method is called when an inspector has been closed.
// Removes reference from memory.
// <param name="id">The unique id of the closed inspector</param>
private void WrapperClosed(Guid id)
{
_wrappedInspectors.Remove(id);
}
private void ThisAddInShutdown(object sender, EventArgs e)
{
// Clean up.
_wrappedInspectors.Clear();
_inspectors.NewInspector -= WrapInspector;
_inspectors = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
#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()
{
Startup += ThisAddInStartup;
Shutdown += ThisAddInShutdown;
}
#endregion
}
There should be enough there to get people out of trouble if the link is down
Related
The following simple multi-threaded program was meant to try out the CommunityToolkit Messenger package for which the documentation says (see: Messenger)
Both WeakReferenceMessenger and StrongReferenceMessenger also expose a Default property that offers a thread-safe implementation built-in into the package.
I had hoped this would mean I could send messages on one thread and receive them on other threads but a problem arose with what seems to be the IMessenger Interface. Details follow below.
This project starts with a vanilla TemplateStudio WinUI 3 (v1.1.5) desktop template that uses the CommunityToolkit Mvvm package (with Messenger) and a single page, MainPage. When the App launches, it starts a RandomMessageGenerator thread that periodically issues a TraceMessage using the WeakReferenceMessenger.Default channel from the Toolkit. The UI thread receives these messages and stores them in a List.
App.xaml.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.UI.Xaml;
using Multi_Window.Activation;
using Multi_Window.Contracts.Services;
using Multi_Window.Core.Contracts.Services;
using Multi_Window.Core.Services;
using Multi_Window.Services;
using Multi_Window.ViewModels;
using Multi_Window.Views;
using Microsoft.UI;
using Microsoft.UI.Windowing;
using CommunityToolkit.Mvvm.Messaging.Messages;
using System.Diagnostics;
namespace Multi_Window;
// To learn more about WinUI 3, see https://docs.microsoft.com/windows/apps/winui/winui3/.
public partial class App : Application
{
// The .NET Generic Host provides dependency injection, configuration, logging, and other services.
// https://docs.microsoft.com/dotnet/core/extensions/generic-host
// https://docs.microsoft.com/dotnet/core/extensions/dependency-injection
// https://docs.microsoft.com/dotnet/core/extensions/configuration
// https://docs.microsoft.com/dotnet/core/extensions/logging
public IHost Host { get; }
public static T GetService<T>()
where T : class
{
if ((App.Current as App)!.Host.Services.GetService(typeof(T)) is not T service)
{
throw new ArgumentException($"{typeof(T)} needs to be registered in ConfigureServices within App.xaml.cs.");
}
return service;
}
public static WindowEx MainWindow { get; } = new MainWindow();
public static ShellPage? ShellPage { get; set; }
private static readonly List<string> _traceMessages = new();
private Task? messageGenerator;
public App()
{
InitializeComponent();
Host = Microsoft.Extensions.Hosting.Host.
CreateDefaultBuilder().
UseContentRoot(AppContext.BaseDirectory).
ConfigureServices((context, services) =>
{
// Default Activation Handler
services.AddTransient<ActivationHandler<LaunchActivatedEventArgs>, DefaultActivationHandler>();
// Other Activation Handlers
// Services
services.AddTransient<INavigationViewService, NavigationViewService>();
services.AddSingleton<IActivationService, ActivationService>();
services.AddSingleton<IPageService, PageService>();
services.AddSingleton<INavigationService, NavigationService>();
// Core Services
services.AddSingleton<IFileService, FileService>();
// Views and ViewModels
services.AddTransient<MainViewModel>();
services.AddTransient<MainPage>();
// ** NOTE ** changed to Singleton so we can refer to THE ShellPage/ShellViewModel
services.AddSingleton<ShellPage>();
services.AddSingleton<ShellViewModel>();
// Configuration
}).
Build();
UnhandledException += App_UnhandledException;
System.AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
Microsoft.UI.Xaml.Application.Current.UnhandledException += Current_UnhandledException;
}
private void RandomMessageGenerator()
{
var shutdown = false;
WeakReferenceMessenger.Default.Register<ShutDownMessage>(this, (r, m) => shutdown = true);
Debug.WriteLine($"RandomMessageGenerator started on thread {Environment.CurrentManagedThreadId}");
Random rnd = new();
// not a good way to control thread shutdown in general but will do for a quick test
while (shutdown == false)
{
Thread.Sleep(rnd.Next(5000));
var tm = new TraceMessage($"{DateTime.Now:hh:mm:ss.ffff} Timer event. (Th: {Environment.CurrentManagedThreadId})");
try
{
WeakReferenceMessenger.Default.Send(tm);
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
break;
}
}
Debug.WriteLine($"RandomMessageGenerator closed at {DateTime.Now:hh:mm:ss.ffff} (Th: {Environment.CurrentManagedThreadId})");
}
private void Current_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e) => throw new NotImplementedException();
private void CurrentDomain_UnhandledException(object sender, System.UnhandledExceptionEventArgs e) => throw new NotImplementedException();
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
{
// TODO: Log and handle exceptions as appropriate.
// https://docs.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.application.unhandledexception.
throw new NotImplementedException();
}
protected async override void OnLaunched(LaunchActivatedEventArgs args)
{
base.OnLaunched(args);
await App.GetService<IActivationService>().ActivateAsync(args);
MainWindow.AppWindow.Closing += OnAppWindowClosing;
WeakReferenceMessenger.Default.Register<TraceMessage>(this, (r, m) =>
{
_traceMessages.Add(m.Value);
Debug.WriteLine(m.Value);
});
WeakReferenceMessenger.Default.Register<WindowClosedMessage>(this, (r, m) => OnStatusWindowClosed()); // StatusWindow closed events
WeakReferenceMessenger.Default.Register<App, TraceMessagesRequest>(this, (r, m) => m.Reply(_traceMessages)); // StatusWindow requests previous messages
messageGenerator = Task.Run(RandomMessageGenerator);
}
private void OnStatusWindowClosed()
{
if (ShellPage is not null && ShellPage.SettingsStatusWindow)
{
ShellPage.SettingsStatusWindow = false; // turn off toggle
if (ShellPage.NavigationFrame.Content is MainPage settingsPage) settingsPage.StatusWindowToggle.IsOn = false;
}
}
private async void OnAppWindowClosing(object sender, AppWindowClosingEventArgs e)
{
WeakReferenceMessenger.Default.UnregisterAll(this); // stop messages and avoid memory leaks
WeakReferenceMessenger.Default.Send(new ShutDownMessage(true)); // close all windows
MainWindow.AppWindow.Closing -= OnAppWindowClosing;
if (messageGenerator is not null) await messageGenerator;
}
}
The user may create a StatusWindow (a secondary Window on the UI thread) by toggling a switch on MainPage. The StatusWindow should open, request and load previous messages from the App, then register for new TraceMessages. All TraceMessages (including new ones) are displayed in a ListView on the StatusWindow.
MainPage.xaml.cs
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Multi_Window.ViewModels;
namespace Multi_Window.Views;
public sealed partial class MainPage : Page
{
public MainViewModel ViewModel { get; } = App.GetService<MainViewModel>();
public ShellPage ShellPage { get; } = App.GetService<ShellPage>();
public ShellViewModel ShellViewModel { get; } = App.GetService<ShellViewModel>();
public MainPage()
{
DataContext = ViewModel;
InitializeComponent();
}
private void StatusWindow_Toggled(object sender, RoutedEventArgs e)
{
if (StatusWindowToggle.IsOn && ShellPage.SettingsStatusWindow == false)
{
StatusWindow window = new() { Title = "Prosper Status" };
window.Activate();
ShellPage.SettingsStatusWindow = true;
}
else if (StatusWindowToggle.IsOn == false && ShellPage.SettingsStatusWindow == true)
WeakReferenceMessenger.Default.Send(new CloseWindowMessage(true));
}
}
MainPage.xaml
<Page
x:Class="Multi_Window.Views.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid x:Name="ContentArea">
<ToggleSwitch x:Name="StatusWindowToggle" x:FieldModifier="public" Grid.Row="2" Grid.Column="1" Header="Show Status Window"
Toggled="StatusWindow_Toggled" IsOn="{x:Bind ShellPage.SettingsStatusWindow, Mode=OneTime}" />
</Grid>
</Page>
StatusWindow.xaml.cs
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.UI.Xaml;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace Multi_Window.Views;
/// <summary>
/// An empty window that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class StatusWindow : Window
{
private ObservableCollection<string> _traceMessages { get; } = new();
public StatusWindow()
{
InitializeComponent();
var sw = new WindowPrimitives(this);
sw.AppWindow.SetIcon("Assets/wip.ico");
WeakReferenceMessenger.Default.Register<CloseWindowMessage>(this, (r, m) => Close());
WeakReferenceMessenger.Default.Register<ShutDownMessage>(this, (r, m) => Close());
}
private void StatusWindow_Closed(object sender, WindowEventArgs args)
{
WeakReferenceMessenger.Default.UnregisterAll(this); // stop getting messages and avoid memory leaks
WeakReferenceMessenger.Default.Send(new WindowClosedMessage(true)); // acknowledge closure
}
private void StatusMessages_Loaded(object sender, RoutedEventArgs e)
{
// get current Trace messages
var messages = WeakReferenceMessenger.Default.Send<TraceMessagesRequest>();
if (messages != null && messages.Responses.Count > 0)
foreach (var response in messages.Responses)
foreach (var trace in response)
_traceMessages.Add(trace);
// register for Trace messages and, when they arrive, add them to list
WeakReferenceMessenger.Default.Register<TraceMessage>(this, (r, m) => _traceMessages.Add(m.Value));
}
}
StatusPage.xaml
<Window
x:Class="Multi_Window.Views.StatusWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Closed="StatusWindow_Closed"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListView x:Name="StatusMessages" x:FieldModifier="public" VerticalAlignment="Top" Margin="20" SelectionMode="None" BorderBrush="Black" BorderThickness="1"
ItemsSource="{x:Bind _traceMessages, Mode=OneWay}"
ScrollViewer.HorizontalScrollMode="Enabled"
ScrollViewer.HorizontalScrollBarVisibility="Visible"
ScrollViewer.IsHorizontalRailEnabled="True"
ScrollViewer.IsDeferredScrollingEnabled="False"
Loaded="StatusMessages_Loaded">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel VerticalAlignment="Bottom" ItemsUpdatingScrollMode="KeepLastItemInView"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
</Grid>
</Window>
Other Sundry Classes
// Allows Win32 access to a Window through WinAPI
public class WindowPrimitives
{
public IntPtr HWnd { get; }
private WindowId WindowId { get; }
public AppWindow AppWindow { get; }
public Window Window { get; }
public WindowPrimitives(Window window)
{
HWnd = WinRT.Interop.WindowNative.GetWindowHandle(window);
WindowId = Win32Interop.GetWindowIdFromWindow(HWnd);
AppWindow = AppWindow.GetFromWindowId(WindowId);
Window = window;
}
}
// Message Definitions
public class CloseWindowMessage : ValueChangedMessage<bool>
{
public CloseWindowMessage(bool value) : base(value) { }
}
public class WindowClosedMessage : ValueChangedMessage<bool>
{
public WindowClosedMessage(bool value) : base(value) { }
}
public class ShutDownMessage : ValueChangedMessage<bool>
{
public ShutDownMessage(bool value) : base(value) { }
}
public class TraceMessage : ValueChangedMessage<string>
{
public TraceMessage(string value) : base(value) { }
}
public class TraceMessagesRequest : CollectionRequestMessage<List<string>>
{
}
The problem is that, on the first new TraceMessage sent after the StatusWindow has been opened, the same WeakReferenceMessenger.Default.Send() method that has been happily sending messages between the RandomMessageGenerator thread and the UI thread throws a "The application called an interface that was marshalled for a different thread. (0x8001010E (RPC_E_WRONG_THREAD))" exception and the RandomMessageGenerator thread dies.
The exception is thrown by the WeakReferenceMessenger.Default.Send(tm); statement in the RandomMessageGenerator() method. I assume the issue is in the IMessenger interface (the only interface involved here). Briefly, as I understand it, this interface builds a table of subscribed receivers for each message type. Each receiver is then signaled on each Send().
One possibility is that references in the receiver list are all assumed to marshal to the same thread. If that were so, none of the messages between threads would work but they do before the StatusWindow is opened so that's unlikely. Changing the list of receivers is an obvious place where threading issues might occur. As WeakReferenceMessenger.Default is thread-safe, I thought adding (and deleting) registered receivers would be thread-safe but doesn't seem to be the case here. Finally, it could be the message itself (a string in this case) that is at fault. I don't know for sure but assumed that the Send method took a private copy of the message to deliver to the marshaled thread.
Could any of you please help me understand the mistake I've made here?
I did find a solution for this particular issue. As expected, it's because an object is accessed from a thread other than the thread on which it was created.
To fix the error, add
public static DispatcherQueue UIDispatcherQueue = DispatcherQueue.GetForCurrentThread();
to the App class which will allow any thread access to the UI thread DispatcherQueue. Then, change the message registration in StatusWindow.xaml.cs to
// register for Trace messages and, when they arrive, add them to list
WeakReferenceMessenger.Default.Register<TraceMessage>(this, (r, m) => App.UIDispatcherQueue.TryEnqueue(() => _traceMessages.Add(m.Value)));
This will now marshal the _traceMessages.Add call in the message handler to the UI thread, which is where
private ObservableCollection<string> _traceMessages { get; } = new();
was constructed.
This was easy enough to figure out once I realized that the point where the exception was thrown and the exception message were both rather deceptive. Although the sending of the message is the cause of the message being received, it's the attempt to handle the message on the wrong thread that really throws the exception.
At any rate, this appears to show that the message receiver handler executes on the same thread as the sender. I was hoping that "thread-safe" in the documentation meant messages were automatically marshalled to the receiver's thread.
I cannot get Undo and Redo to behave correctly when using a dialog.
I have a simple model with a property indicating the state of the object(running, paused, stopped) which can be altered via a dialog. What happens is that I get actions that seems to do nothing in my undo queue or undo restores the object to an intermediate state.
The model object is registered with memento in the constructor. The dialog has three radio buttons each representing one of the three different states. Each radio button is bind to a command each. Each command performs a change of the property. I have tried two different approaches, either each command sets the property directly in the object or each command sets an instance variable for the view model when called and then I use the Saving event to modify the object.
If using the first approach each property change is put on the Undo queue if the user clicks on more than just one radiobutton before clicking Ok in the dialog. Tried to solve that by wrapping the whole dialog into a batch but that results in undoing the state change the object is restored to the state it had before the final one, i.e. if the property was set to stopped before the dialog opened and the user pressed the pause radiobutton, then start one and finally Ok, undo will set the property to paused instead of the expected stopped.
If using the second approach the user opens the dialog, change the state to paused, click Ok in the dialog the undo/redo behaves as expected but if the dialog is opened again and Cancel is chosen one more action is added to the Undo queue, i.e. the user has to click Undo twice to get back to the initial stopped-state.
So my question is how should this be correctly implemented to get the expected behaviour; that each dialog interaction can be undone and not every interaction in the dialog?
Here is the code for the ViewModel:
namespace UndoRedoTest.ViewModels
{
using Catel.Data;
using Catel.MVVM;
public class StartStopViewModel : ViewModelBase
{
Machine.MachineState _state;
public StartStopViewModel(Machine controlledMachine)
{
ControlledMachine = controlledMachine;
_state = controlledMachine.State;
StartMachine = new Command(OnStartMachineExecute);
PauseMachine = new Command(OnPauseMachineExecute);
StopMachine = new Command(OnStopMachineExecute);
Saving += StartStopViewModel_Saving;
}
void StartStopViewModel_Saving(object sender, SavingEventArgs e)
{
ControlledMachine.State = _state;
}
[Model]
public Machine ControlledMachine
{
get { return GetValue<Machine>(ControlledMachineProperty); }
private set { SetValue(ControlledMachineProperty, value); }
}
public static readonly PropertyData ControlledMachineProperty = RegisterProperty("ControlledMachine", typeof(Machine));
public override string Title { get { return "Set Machine state"; } }
public Command StartMachine { get; private set; }
public Command PauseMachine { get; private set; }
public Command StopMachine { get; private set; }
private void OnStartMachineExecute()
{
_state = Machine.MachineState.RUNNING;
//ControlledMachine.SecondState = Machine.MachineState.RUNNING;
}
private void OnPauseMachineExecute()
{
_state = Machine.MachineState.PAUSED;
//ControlledMachine.SecondState = Machine.MachineState.PAUSED;
}
private void OnStopMachineExecute()
{
_state = Machine.MachineState.STOPPED;
//ControlledMachine.SecondState = Machine.MachineState.STOPPED;
}
}
}
First of all, don't subscribe to the Saving event but simply override the Save() method. Note that Catel handles the model state for you when you decorate a model with the ModelAttribute. Therefore you need to get the prestate and poststate of the dialog and then push the result set into a batch.
For example, I would create extension methods for the object class (or model class) like this:
public static Dictionary<string, object> GetProperties(this IModel model)
{
// todo: return properties
}
Then you do this in the Initialize and in the Save method and you would have 2 sets of properties (pre state and post state). Now you have that, it's easy to calculate the differences:
public static Dictionary<string, object> GetChangedProperties(Dictionary<string, object> preState, Dictionary<string, object> postState)
{
// todo: calculate difference
}
Now you have the difference, you can create a memento batch and it would restore the exact state as you expected.
ps. it would be great if you could put this into a blog post once done or create a PR with this feature
I'm using Windows Azure Mobile Services to store and retrieve data in my Windows Phone 8 app. This is a bit of a complicated issue so I will do my best to explain it.
Firstly I'm using raw push notifications to receive a message and when it receives the message it updates a listbox in my app. When I open my app, navigate to the page with the ListBox and receive a push notification the ListBox updates fine. If I press back, then navigate to the same page with the ListBox, the push notification is received, the code to update the ListBox executes with no errors yet the ListBox doesn't update. I have checked that the same code runs using the OnNavigatedTo handler in both scenarios, but it seems like the ListBox does not bind correctly in the second instance when I press back and then re-navigate to the same page. Here are some code snippets:
MobileServiceCollection declarations:
public class TodoItem
{
public int Id { get; set; }
[JsonProperty(PropertyName = "text")]
public string Text { get; set; }
}
private MobileServiceCollection<ToDoItem, ToDoItem> TodoItems;
private IMobileServiceTable<TodoItem> todoTable = App.MobileService.GetTable<TodoItem>();
Push Notification Received Handler:
void PushChannel_HttpNotificationReceived(object sender, HttpNotificationEventArgs e)
{
string message;
using (System.IO.StreamReader reader = new System.IO.StreamReader(e.Notification.Body))
{
message = reader.ReadToEnd();
}
Dispatcher.BeginInvoke(() =>
{
var todoItem = new TodoItem
{
Text = message,
};
ToDoItems.Add(todoItem);
}
);
}
I have tried using:
ListItems.UpdateLayout();
and
ListItems.ItemsSource = null;
ListItems.ItemsSource = ToDoItems;
before and after the code in the above procedure that adds the ToDoItem but it didn't help.
The following procedure is called in my OnNavigatedTo event handler, and refreshes the Listbox and assigns ToDoItems as the items source:
private async void RefreshTodoItems()
{
try
{
ToDoItems = await todoTable
.ToCollectionAsync();
}
catch (MobileServiceInvalidOperationException e)
{
MessageBox.Show(e.Message, "Error loading items", MessageBoxButton.OK);
}
ListItems.ItemsSource = ToDoItems;
}
The above procedure is async but I have made sure it completes before receiving any notifications. Even so, as mentioned above when I open the app, navigate to the page that shows the ListBox it updates fine. When I press back, navigate to the same page again, it doesn't work. When I back out of the app, re-open it, navigate to the page with the ListBox, it works again, and then fails if I press back and re-open the page. So it seems the ListBox is not binding to ToDoItems correctly when I press back and navigate to the same page.
Any help appreciated. Thanks.
Can you modify your approach a bit to use Data Binding and the MVVM model to bind your model to your view.
It might look like a bit of effort initially but will save you a lot of debugging hours later on.
Just follow the below steps
Create a new class that implements INotifyPropertyChanged
Add the below method implementation
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Add public ObservableCollection<TodoItem> TodoItems{ get; private set; } and initialize it in the constructor.
Every PhoneApplicationPage has a DataContext member. Assing it to a singleton instance of the above class that you create.
In the XAML, add the property ItemsSource="{Binding TodoItems}" to the list.
In the DataTemplate of the list use ItemsSource="{Binding Text}" for the control you wish to display this value on. ( e.g. TextBlock )
Now whenever you add elements to the collection, it will be reflected in the UI, and vice-versa.
I have a main Activity (OceanintelligenceActivity). In this activity I register the device for push notifications and also I registered a receiver that shows a Dialog and starts the proper Activity depending on the info sent from my server. This is the code I'm using to register the device and the receiver :
protected void gcmRegistration(){
PMApplication thisApp = PMApplication.getInstance();
AppDelegate delegate = thisApp.getAppDelegate();
final Context context = this;
// Make sure the device has the proper dependencies.
GCMRegistrar.checkDevice(this);
// Make sure the manifest was properly set - comment out this line
// while developing the app, then uncomment it when it's ready.
GCMRegistrar.checkManifest(this);
// Let's declare our receiver
registerReceiver(mHandleMessageReceiver,new IntentFilter(DISPLAY_MESSAGE_ACTION));
final String regId = GCMRegistrar.getRegistrationId(this);
if (regId.equals("")) {
Log.d("", "Lets register for Push");
GCMRegistrar.register(this, SENDER_ID);
}else {
if(GCMRegistrar.isRegisteredOnServer(this)) {
// Skips registration.
String apnsToken = delegate.sso.getAPNSToken();
if(!apnsToken.equals(regId)){
Log.d("", "The Device RegId has changed on GCM Servers");
// We should let our servers know about this
ServerUtilities.update(regId, context);
}
} else {
Log.d("","Is not register on PM Server");
// Try to register again, but not in the UI thread.
// It's also necessary to cancel the thread onDestroy(),
// hence the use of AsyncTask instead of a raw thread.
mRegisterTask = new AsyncTask<Void, Void, Void>() {
#Override
protected Void doInBackground(Void... params) {
boolean registered = ServerUtilities.register(context, regId);
// At this point all attempts to register with the app
// server failed, so we need to unregister the device
// from GCM - the app will try to register again when
// it is restarted. Note that GCM will send an
// unregistered callback upon completion, but
// GCMIntentService.onUnregistered() will ignore it.
if (!registered) {
GCMRegistrar.unregister(context);
}
return null;
}
#Override
protected void onPostExecute(Void result) {
mRegisterTask = null;
}
};
mRegisterTask.execute(null, null, null);
}
}
}
This is how I set the receiver:
private final BroadcastReceiver mHandleMessageReceiver =
new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String newMessage = intent.getExtras().getString(EXTRA_MESSAGE);
Log.d("","BroadcastReceiver onReceive");
notificationIntent = GCMIntentService.getNotificationIntent(context);
new AlertDialog.Builder(context)
.setMessage(newMessage+". Would you like to see it right now?")
.setCancelable(false)
.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// Show update
startActivity(notificationIntent);
}
})
.setNegativeButton("No", null).show();
}
};
GCMIntentService.getNotificationIntent(context). This line returns the the Intent with the Activity I want to start.
Whenever there is a notification onReceive gets called but the Dialog only shows if I am on the main activity. So if the app is on a different activity, onReceive still gets called but the dialog doesn't show and therefore I can't start the proper activity.
How can I display a dialog on Currently visible activity on BroadcastReceiver?
Playing around with this one and searching on google I came across a solution. It is not the best one but it works. I still can't believe there is not an easy way to get the current context in Android. So this is what I did to manage to show the Dialog regardless of what the current activity is : I have a public static property of type Context on my singleton class(AppDelegate) and on each activity I override the onResume method and set the Context to the current activity like this AppDelegate.CURRENT_CONTEXT = this. Then on my dialog : AlertDialog.Builder(AppDelegate.CURRENT_CONTEXT).....
I am designing a static message bus that would allow subscribing to and publishing of messages of an arbitrary type. To avoid requiring observers to unsubscribe explicitly, I would like to keep track of WeakReference objects that point to delegates instead of tracking delegates themselves. I ended up coding something similar to what Paul Stovell described in his blog http://www.paulstovell.com/weakevents.
My problem is this: as opposed to Paul's code, my observers subscribe to messages on one thread, but messages may be published on another. In this case, I observe that by the time I need to notify observers, my WeakReference.Target values are null indicating that targets have been collected, even though I know for certain they weren't. The problem persists for both short and long weak references.
Conversely, when subscribing and publishing is done from the same thread, the code works fine. The latter is true even if I actually end up enumerating over targets on a new thread from ThreadPool, for as long as the request initially comes from the same thread I subscribe to messages on.
I understand that this is a very specific case, so any help is greatly appreciated.
My question is: should I not be able to reliably access WeakReference objects from multiple threads provided proper thread synchronization is in place? It appears that I cannot, which does not make much sense to me. So, what am I not doing right?
It looks like after reducing my code to a simpler form (see below), it now works fine. This means, the problem that caused weak reference targets to be collected too early must reside elsewhere in my code. So, to answer my own question, it appears that weak references can be securely accessed from multiple threads.
Here is my test code:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
namespace Test
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Starting the app");
Test test = new Test();
// uncomment these lines to cause automatic unsubscription from Message1
// test = null;
// GC.Collect();
// GC.WaitForPendingFinalizers();
// publish Message1 on this thread
// MessageBus.Publish<Message1>(new Message1());
// publish Message1 on another thread
ThreadPool.QueueUserWorkItem(delegate
{
MessageBus.Publish<Message1>(new Message1());
});
while (!MessageBus.IamDone)
{
Thread.Sleep(100);
}
Console.WriteLine("Exiting the app");
Console.WriteLine("Press <ENTER> to terminate program.");
Console.WriteLine();
Console.ReadLine();
}
}
public class Test
{
public Test()
{
Console.WriteLine("Subscribing to message 1.");
MessageBus.Subscribe<Message1>(OnMessage1);
Console.WriteLine("Subscribing to message 2.");
MessageBus.Subscribe<Message2>(OnMessage2);
}
public void OnMessage1(Message1 message)
{
Console.WriteLine("Got message 1. Publishing message 2");
MessageBus.Publish<Message2>(new Message2());
}
public void OnMessage2(Message2 message)
{
Console.WriteLine("Got message 2. Closing the app");
MessageBus.IamDone = true;
}
}
public abstract class MessageBase
{
public string Message;
}
public class Message1 : MessageBase
{
}
public class Message2 : MessageBase
{
}
public static class MessageBus
{
// This is here purely for this test
public static bool IamDone = false;
/////////////////////////////////////
/// <summary>
/// A dictionary of lists of handlers of messages by message type
/// </summary>
private static ConcurrentDictionary<string, List<WeakReference>> handlersDict = new ConcurrentDictionary<string, List<WeakReference>>();
/// <summary>
/// Thread synchronization object to use with Publish calls
/// </summary>
private static object _lockPublishing = new object();
/// <summary>
/// Thread synchronization object to use with Subscribe calls
/// </summary>
private static object _lockSubscribing = new object();
/// <summary>
/// Creates a work queue item that encapsulates the provided parameterized message
/// and dispatches it.
/// </summary>
/// <typeparam name="TMessage">Message argument type</typeparam>
/// <param name="message">Message argument</param>
public static void Publish<TMessage>(TMessage message)
where TMessage : MessageBase
{
// create the dictionary key
string key = String.Empty;
key = typeof(TMessage).ToString();
// initialize a queue work item argument as a tuple of the dictionary type key and the message argument
Tuple<string, TMessage, Exception> argument = new Tuple<string, TMessage, Exception>(key, message, null);
// push the message on the worker queue
ThreadPool.QueueUserWorkItem(new WaitCallback(_PublishMessage<TMessage>), argument);
}
/// <summary>
/// Publishes a message to the bus, causing observers to be invoked if appropriate.
/// </summary>
/// <typeparam name="TArg">Message argument type</typeparam>
/// <param name="stateInfo">Queue work item argument</param>
private static void _PublishMessage<TArg>(Object stateInfo)
where TArg : class
{
try
{
// translate the queue work item argument to extract the message type info and
// any arguments
Tuple<string, TArg, Exception> arg = (Tuple<string, TArg, Exception>)stateInfo;
// call all observers that have registered to receive this message type in parallel
Parallel.ForEach(handlersDict.Keys
// find the right dictionary list entry by message type identifier
.Where(handlerKey => handlerKey == arg.Item1)
// dereference the list entry by message type identifier to get a reference to the observer
.Select(handlerKey => handlersDict[handlerKey]), (handlerList, state) =>
{
lock (_lockPublishing)
{
List<int> descopedRefIndexes = new List<int>(handlerList.Count);
// search the list of references and invoke registered observers
foreach (WeakReference weakRef in handlerList)
{
// try to obtain a strong reference to the target
Delegate dlgRef = (weakRef.Target as Delegate);
// check if the underlying delegate reference is still valid
if (dlgRef != null)
{
// yes it is, get the delegate reference via Target property, convert it to Action and invoke the observer
try
{
(dlgRef as Action<TArg>).Invoke(arg.Item2);
}
catch (Exception e)
{
// trouble invoking the target observer's reference, mark it for deletion
descopedRefIndexes.Add(handlerList.IndexOf(weakRef));
Console.WriteLine(String.Format("Error looking up target reference: {0}", e.Message));
}
}
else
{
// the target observer's reference has been descoped, mark it for deletion
descopedRefIndexes.Add(handlerList.IndexOf(weakRef));
Console.WriteLine(String.Format("Message type \"{0}\" has been unsubscribed from.", arg.Item1));
MessageBus.IamDone = true;
}
}
// remove any descoped references
descopedRefIndexes.ForEach(index => handlerList.RemoveAt(index));
}
});
}
// catch all Exceptions
catch (AggregateException e)
{
Console.WriteLine(String.Format("Error dispatching messages: {0}", e.Message));
}
}
/// <summary>
/// Subscribes the specified delegate to handle messages of type TMessage
/// </summary>
/// <typeparam name="TArg">Message argument type</typeparam>
/// <param name="action">WeakReference that represents the handler for this message type to be registered with the bus</param>
public static void Subscribe<TArg>(Action<TArg> action)
where TArg : class
{
// validate input
if (action == null)
throw new ArgumentNullException(String.Format("Error subscribing to message type \"{0}\": Specified action reference is null.", typeof(TArg)));
// build the queue work item key identifier
string key = typeof(TArg).ToString();
// check if a message of this type was already added to the bus
if (!handlersDict.ContainsKey(key))
{
// no, it was not, create a new dictionary entry and add the new observer's reference to it
List<WeakReference> newHandlerList = new List<WeakReference>();
handlersDict.TryAdd(key, newHandlerList);
}
lock (_lockSubscribing)
{
// append this new observer's reference to the list, if it does not exist already
if (!handlersDict[key].Any(existing => (existing.Target as Delegate) != null && (existing.Target as Delegate).Equals(action)))
{
// append the new reference
handlersDict[key].Add(new WeakReference(action, true));
}
}
}
}
}
This is an amendment to my previous answer. I have discovered why my original code did not work and this information may be useful for others. In my original code MessageBus was instantiated as Singleton:
public class MessageBus : Singleton<MessageBus> // Singleton<> is my library class
In the example above, it was declared as static:
public static class MessageBus
Once I converted my code to use a static, things started working. Having said that, I could not yet figure out why the singleton did not work.