I am having some issues with bindings using xamarin forms IOS.
I have bindings That display a loading label in the center of the screen when the first page appears.
After The listview loads the data the Loading label is hidden through bindings and the list view is displayed.
In the android project I have in this repo Xamarin forms IOS Android Test
It works fine. In the ios in the repro project the loading label is hidden when the application first loads and will display if you tap on the screen.
If you continue the loading label disappears and you see a blank screen but if you tap on the screen again the list data view appears.
I am thinking this is a bug with xamarin but I am hoping that my bindings are just incorrect. I work around for this issue would be appreciated.
I also two other problems. When you click on a list view item and the navigate back the data is not refreshed because onappearing is not triggered.
In android it is triggered without an issue. Sometimes onappearing does trigger after you tap on the screen like the steps mentioned above.
The last issue is that when switching between tabs IOS does not trigger on appearing. With android it does.
Any help on this issue would be greatly appreciated. I have done extensive searching on solutions for these problems and have yet to find and answer.
Thanks!
Here is some code snippets to help. This code is also in the GitHub repo if you want to test it out.
In XAML
<Label x:Name="xEmptyListView"
FontSize="16"
HorizontalOptions="CenterAndExpand"
HorizontalTextAlignment="Center"
VerticalOptions="CenterAndExpand"
Text="{Binding ListViewVisibleText, Mode=TwoWay}"
IsVisible="{Binding IsListViewLabelEmptyVisible, Mode=TwoWay }"/>
<ListView x:Name="ItemsListView"
ItemsSource="{Binding Items}"
VerticalOptions="FillAndExpand"
HasUnevenRows="true"
RefreshCommand="{Binding LoadItemsCommand}"
IsPullToRefreshEnabled="true"
IsRefreshing="{Binding IsBusy, Mode=OneWay}"
CachingStrategy="RecycleElement"
ItemSelected="OnItemSelected"
IsVisible="{Binding IsListViewVisible, Mode=TwoWay}"
IsEnabled="{Binding IsActivityRunning, Mode=TwoWay, Converter={StaticResource InverseBooleanConverter}}">
ViewModel
private bool activityRunning { get; set; }
public bool IsActivityRunning
{
get { return activityRunning; }
set
{
if (activityRunning == value)
return;
activityRunning = value;
OnPropertyChanged("IsActivityRunning");
}
}
private string listViewVisibleText { get; set; }
public string ListViewVisibleText
{
get { return listViewVisibleText; }
set
{
if (listViewVisibleText == value)
return;
listViewVisibleText = value;
OnPropertyChanged("ListViewVisibleText");
}
}
private bool listViewLabelEmptyVisible { get; set; }
public bool IsListViewLabelEmptyVisible
{
get
{
if (Items == null || Items.Count == 0)
{
if (IsBusy)
{
ListViewVisibleText = "Loading...";
}
else
{
ListViewVisibleText = "No Items found";
}
listViewLabelEmptyVisible = true;
}
else
{
ListViewVisibleText = string.Empty;
listViewLabelEmptyVisible = false;
}
OnPropertyChanged("IsListViewLabelEmptyVisible");
return listViewLabelEmptyVisible;
}
}
private bool listViewVisible { get; set; }
public bool IsListViewVisible
{
get
{
if (Items == null || Items.Count == 0)
{
listViewVisible = false;
}
else
{
listViewVisible = true;
}
OnPropertyChanged("IsListViewVisible");
return listViewVisible;
}
}
XAML.cs
protected override void OnAppearing()
{
base.OnAppearing();
viewModel.LoadItemsCommand.Execute(null);
}
I am using notify property changed. It is standard code
This is what my view model inherits for the notifyproperty changed
public class ItemsViewModel : BaseViewModel
which base viewmodel inherits from the observable object
public class BaseViewModel : ObservableObject
When you create a test xamarin project this is the way it looks.
public class ObservableObject : INotifyPropertyChanged
{
/// <summary>
/// Sets the property.
/// </summary>
/// <returns><c>true</c>, if property was set, <c>false</c> otherwise.</returns>
/// <param name="backingStore">Backing store.</param>
/// <param name="value">Value.</param>
/// <param name="propertyName">Property name.</param>
/// <param name="onChanged">On changed.</param>
/// <typeparam name="T">The 1st type parameter.</typeparam>
protected bool SetProperty<T>(
ref T backingStore, T value,
[CallerMemberName]string propertyName = "",
Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
/// <summary>
/// Occurs when property changed.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises the property changed event.
/// </summary>
/// <param name="propertyName">Property name.</param>
protected void OnPropertyChanged([CallerMemberName]string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Activity running gets set when items are loading etc.
This all works on android. IOS it doesn't.
Sometimes the navigation behaviours are different from iOS and Android. If OnAppearing is not working, you can try to use MessageCenter https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/messaging-center/ to trigger data loading.
I figured this one out. The way I was handling the bindings with showing the label and hiding the list view and vice versa was interfering with the way that IOS handles requests. I removed the bindings and set the controls directly using a property changed handler on the list view. That fixed the on appearing problems for the tabs, the back button etc.
Related
I would to put a svg embedded image as ImageSource for a Button in Xamarin.Forms, something like this
<Button Text="Button" ImageSource="resource://fullname.svg">
</Button>
possibly applying a Transformation to svg (from FFImageLoading.Transformations), but this is a plus.
I've tried this syntax
<Button Text="Button"
ImageSource="{ext:ImageResourceExtension fullname.svg}" />
c#
public class ImageResourceExtension : IMarkupExtension
{
private static Assembly Assembly = typeof(ImageResourceExtension).GetTypeInfo().Assembly;
public string Source { get; set; }
public object ProvideValue(IServiceProvider serviceProvider)
{
if (Source == null) return null;
return SvgImageSource.FromResource(Source, Assembly, 32, 32);
}
}
But it's not working.
Moreover I can't make working this syntax
Source="{resource://fullname.svg, Converter={StaticResource SvgImageSourceConverter}}"
Any help?
Thanks
As Jason said, FFImageLoading support SVG files. Follow the steps below.
Create a Resource folder in your Xamarin.Forms instead of Android part. And then add the SVG file as Embedded resource.
Usage: Use SvgCachedImage to show the embedded svg image and use TapGestureRecognizer to simulate the button click event.
<ffimageloadingsvg:SvgCachedImage
HeightRequest="50"
Source="resource://XamarinDemo.Resources.brightness2.svg"
WidthRequest="50">
<ffimageloadingsvg:SvgCachedImage.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped"></TapGestureRecognizer>
</ffimageloadingsvg:SvgCachedImage.GestureRecognizers>
</ffimageloadingsvg:SvgCachedImage>
Do not forget to add namespace.
xmlns:ffimageloadingsvg="clr-namespace:FFImageLoading.Svg.Forms;assembly=FFImageLoading.Svg.Forms"
Updated: We could use SkiaSharp to draw a image with svg file.
MyControl.cs
public class MyControl : Frame
{
private readonly SKCanvasView _canvasView = new SKCanvasView();
public MyControl()
{
Padding = new Thickness(0);
BackgroundColor = Color.Transparent;
Content = _canvasView;
_canvasView.PaintSurface += CanvasViewOnPaintSurface;
}
public static readonly BindableProperty ImageProperty = BindableProperty.Create(
nameof(Image), typeof(string), typeof(MyControl), default(string), propertyChanged: RedrawCanvas);
public string Image
{
get => (string)GetValue(ImageProperty);
set => SetValue(ImageProperty, value);
}
private static void RedrawCanvas(BindableObject bindable, object oldvalue, object newvalue)
{
MyControl svgIcon = bindable as MyControl;
svgIcon?._canvasView.InvalidateSurface();
}
private void CanvasViewOnPaintSurface(object sender, SKPaintSurfaceEventArgs e)
{
SKCanvas canvas = e.Surface.Canvas;
canvas.Clear();
using (Stream stream = GetType().Assembly.GetManifestResourceStream(Image))
{
SkiaSharp.Extended.Svg.SKSvg svg = new SkiaSharp.Extended.Svg.SKSvg();
svg.Load(stream);
SKImageInfo info = e.Info;
canvas.Translate(info.Width / 2f, info.Height / 2f);
SKRect bounds = svg.ViewBox;
float xRatio = info.Width / bounds.Width;
float yRatio = info.Height / bounds.Height;
float ratio = Math.Min(xRatio, yRatio);
canvas.Scale(ratio);
canvas.Translate(-bounds.MidX, -bounds.MidY);
canvas.DrawPicture(svg.Picture);
}
}
}
Usage:
<local:MyControl
HeightRequest="50"
Image="XamarinDemo.Resources.brightness2.svg"
WidthRequest="50" />
And you could use TapGestureRecognizer to simulate the button click event.
Updated original image resource extension class for Xamarin. OP's class had two issues:
Missing ContentProperty attribute which allows to skip "Source" property name.
Part where partial resource name is converted to a full resource name.
Added optional "Assembly" property, which allows to specify different assembly. In the code, you can also get the full name, resource path, and ImageSource object with the optional replace string map applied, which allows to modify the original SVG content on the fly, e.g. colors).
Usage:
<ff:SvgCachedImage Source="{ImageFromResource file_name.svg}" />
<ff:SvgCachedImage Source="{ImageFromResource Icons/file_name.svg}" />
<Image Source="{ImageFromResource file_name.png}" />
<Image Source="{ImageFromResource file_name.png, Assembly=MyAssembly}" />
ImageFromResourceExtension class:
using FFImageLoading.Svg.Forms;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
/// <summary>
/// Get image source from assembly resource by using partial name.
/// </summary>
/// <example>
/// <ff:SvgCachedImage Source="{ImageFromResource file_name.svg}" />
/// <ff:SvgCachedImage Source="{ImageFromResource Icons/file_name.svg}" />
/// <ff:Image Source="{ImageFromResource file_name.png}" />
/// <ff:Image Source="{ImageFromResource file_name.png, Assembly=MyAssembly}" />
/// </example>
/// <remarks>
/// Parameter format without extension:
/// Source="resource://{AssemblyName}.{PathName}.{FileName}"
/// </remarks>
[ContentProperty(nameof(Source))]
public class ImageFromResourceExtension : IMarkupExtension
{
public static Assembly DefaultAssembly = typeof(ImageResourceExtension).GetTypeInfo().Assembly;
public string Source { get; set; }
public string Assembly { get; set; }
public object ProvideValue(IServiceProvider serviceProvider)
=> GetResourcePath(Source, Assembly);
public static string GetResourcePath(string name, string assembly = null)
=> "resource://" + GetFullName(name, assembly);
public static string GetFullName(string name, string assembly = null)
{
if (string.IsNullOrEmpty(name))
return null;
// Replace folder separators with dots.
name = name.Replace('/', '.');
// Use different assembly if specified.
var asm = string.IsNullOrEmpty(assembly)
? DefaultAssembly
: System.Reflection.Assembly.Load(assembly);
// Find full name of the resource by partial name.
var fullName = asm.GetManifestResourceNames()
.FirstOrDefault(x => x.EndsWith("." + name));
return fullName;
}
public static ImageSource GetImage(string name, string assembly = null, StringDictionaryCollection map = null)
{
var fullName = GetFullName(name);
if (fullName == null)
return null;
// Use different assembly if specified.
var asm = string.IsNullOrEmpty(assembly)
? DefaultAssembly
: System.Reflection.Assembly.Load(assembly);
// Return image source.
return fullName.EndsWith(".svg", StringComparison.OrdinalIgnoreCase)
? SvgImageSource.FromResource(fullName, asm, replaceStringMap: map)
: ImageSource.FromResource(fullName, asm);
}
}
I am creating a kiosk–like application for a Windows 10 PC with a touch screen as its only input interface. What I want to remove is the white touch dot that is displayed as a visual touch feedback (mostly together with a circle, which can be turned off).
Does anyone know how to do this?
I have already searched the registry if there was a cursor (*.cur) file which is used but did not find any results. Due to that I guess that the touch feedback is displayed differently.
Just to make sure — I do not want to lose touch functionality, only the visual feedback needs to be gone.
For WPF programs:
In order to remove the touch feedback from WPF elements, it's enough to set Stylus.IsTapFeedbackEnabled dependency property to false. You may want to do that within styles:
<Style x:Key="yourStyle">
<Setter Property="Stylus.IsTapFeedbackEnabled" Value="False" />
</Style>
The white touch dot you cited is actually a cursor. Whenever you touch the screen, Windows replaces the cursor set for the control with the touch cursor. To avoid this honestly ugly behavior, you can set your cursor to Cursors.None, so that the cursor will be hidden. Paste the code below inside your Window and you won't see your white touch dot anymore.
protected override void OnPreviewTouchDown(TouchEventArgs e)
{
base.OnPreviewTouchDown(e);
Cursor = Cursors.None;
}
protected override void OnPreviewTouchMove(TouchEventArgs e)
{
base.OnPreviewTouchMove(e);
Cursor = Cursors.None;
}
protected override void OnGotMouseCapture(MouseEventArgs e)
{
base.OnGotMouseCapture(e);
Cursor = Cursors.Arrow;
}
protected override void OnPreviewMouseMove(MouseEventArgs e)
{
base.OnPreviewMouseMove(e);
if (e.StylusDevice == null)
Cursor = Cursors.Arrow;
}
Your cursor will always be set to Cursors.Arrow when you're using the mouse, though, so you could lose a different cursor you had set to the window. It'd be an easy task to tweak this behavior — my code only serves for demonstration purposes.
For Windows Forms programs:
Looking at the Reference Source for Stylus.IsTapFeedbackEnabled, I've figured out the P/Invoke calls that happen under the hood. Here's what I've come up with for Windows Form applications:
using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace TouchableWinform
{
public class TouchableForm : Form
{
private const string TOUCH_SUPPORT_CATEGORY = "Touch support";
private const bool IS_PRESS_AND_HOLD_ENABLED_DEFAULT_VALUE = false;
private const bool IS_FLICKS_ENABLED_DEFAULT_VALUE = false;
private const bool IS_TAP_FEEDBACK_DEFAULT_VALUE = false;
private const bool IS_TOUCH_FEEDBACK_DEFAULT_VALUE = false;
/// <summary>
/// Gets or sets a values indicating whether press and hold is enabled.
/// </summary>
[Category(TOUCH_SUPPORT_CATEGORY), Description("Gets or sets a values indicating whether press and hold is enabled."), EditorBrowsable(EditorBrowsableState.Always), DefaultValue(IS_PRESS_AND_HOLD_ENABLED_DEFAULT_VALUE)]
public bool IsPressAndHoldEnabled
{
get;
set;
} = IS_PRESS_AND_HOLD_ENABLED_DEFAULT_VALUE;
/// <summary>
/// Gets or sets a value indicating whether flicks are enabled.
/// </summary>
[Category(TOUCH_SUPPORT_CATEGORY), Description("Gets or sets a value indicating whether flicks are enabled."), EditorBrowsable(EditorBrowsableState.Always), DefaultValue(IS_FLICKS_ENABLED_DEFAULT_VALUE)]
public bool IsFlicksEnabled
{
get;
set;
} = IS_FLICKS_ENABLED_DEFAULT_VALUE;
/// <summary>
/// Gets or sets whether a value indicating whether tap feedback is enabled.
/// </summary>
[Category(TOUCH_SUPPORT_CATEGORY), Description("Gets or sets whether a value indicating whether tap feedback is enabled."), EditorBrowsable(EditorBrowsableState.Always), DefaultValue(IS_TAP_FEEDBACK_DEFAULT_VALUE)]
public bool IsTapFeedbackEnabled
{
get;
set;
} = IS_TAP_FEEDBACK_DEFAULT_VALUE;
/// <summary>
/// Gets or sets whether a value indicating whether touch feedback is enabled.
/// </summary>
[Category(TOUCH_SUPPORT_CATEGORY), Description("Gets or sets whether a value indicating whether touch feedback is enabled."), EditorBrowsable(EditorBrowsableState.Always), DefaultValue(IS_TOUCH_FEEDBACK_DEFAULT_VALUE)]
public bool IsTouchFeedbackEnabled
{
get;
set;
} = IS_TOUCH_FEEDBACK_DEFAULT_VALUE;
/// <summary>
/// Processes Windows messages.
/// </summary>
/// <param name="m">The Windows <see cref="Message"/> to process.</param>
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case 0x2CB:
m.Result = new IntPtr(1);
break;
case 0x2CC:
uint flags = 0;
if (!IsPressAndHoldEnabled)
flags |= 1;
if (!IsTapFeedbackEnabled)
flags |= 8;
flags |= IsTouchFeedbackEnabled ? (uint)0x100 : 0x200;
if (!IsFlicksEnabled)
flags |= 0x10000;
m.Result = new IntPtr(flags);
break;
default:
base.WndProc(ref m);
break;
}
}
}
}
Unfortunately, for some reason I couldn't still figure out, it doesn't quite look to be working. I'm open to any suggestions.
In Windows 10, you can use pointer messages to handle touches. This would make WPF act like native UWP app behavior. The white dot will disappear and touch move animation get fixed.
If you are using .NET Core, add this line to your App.xaml.cs constructor:
public partial class App : Application
{
public App()
{
AppContext.SetSwitch("Switch.System.Windows.Input.Stylus.EnablePointerSupport", true);
}
}
For .NET Framework, you should add App.config file to project. The contents are shown below:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7" />
</startup>
<runtime>
<AppContextSwitchOverrides value="Switch.System.Windows.Input.Stylus.EnablePointerSupport=true" />
</runtime>
</configuration>
Sources:
How to enable pointer message support in WPF .NET Core
WPF will have a touch offset after trun on the WM_Pointer message
I am creating a UIActivityIndicatorView in my Controller.ViewDidLoad
UIActivityIndicatorView spinner = new UIActivityIndicatorView();
spinner.StartAnimating();
spinner.Hidden = true;
this.Add(spinner);
Then I am binding it with MVVMCross
var set = this.CreateBindingSet<TipView, TipViewModel>();
set.Bind(spinner).For(v => v.Hidden).To(vm => vm.IsBusy).WithConversion("Inverse");
When the View initially loads the UIActivityIndicatorView is spinning and visible. This is incorrect as the IsBusy property is being explicitly set to False in the ViewModel's Init(). I can see this happening and I can see the Converter invert the value.
I know the binding is properly connected because if I fire a command that updates the IsBusy property the Indicator is shown and hidden as I would expect. It is just the initial state that is incorrect.
The StartAnimating method seems to cause the Hidden flag to be overridden. If I do not call StartAnimating the Indicator hides and shows as expected. Of course that means I have a non animating
Indicator.
I could get a WeakReference to the VM, listen to PropertyChanged and call StartAnimating but that seems a bit rubbish.
Does anyone have any better ideas?
Some options you can do:
Subscribe to PropertyChanged changes and write custom code in the event handler (as you suggest in your question)
Inherit from UIActivityIndicatorView and write a public get;set; property which provides the composite functionality (calling Start and Hidden) in the set handler
public class MyIndicatorView : UIActivityIndicatorView {
// ctors
private bool _superHidden;
public bool SuperHidden {
get { return _supperHidden; }
set { _superHidden = value; if (!value) StartAnimating() else StopAnimating(); Hidden = value; }
}
}
Provide a View public get;set; property and put the composite functionality in that (e.g. set.Bind(this).For(v => v.MyAdvanced)...
private bool _myAdvanced;
public bool MyAdvanced {
get { return myAdvanced; }
set { myAdvanced = value; if (!value) _spinner.StartAnimating() else _spinner.StopAnimating(); _spinner.Hidden = value; }
}
Write a custom binding for Hidden which replaces the default functionality and contains the combined Start and Hidden calls (for more on custom bindings, there's a couple of N+1 tutorials)
After reading #slodge's reply I went down the road of Weak Event Listener and ran the code to Hide and StartAnimating in the View. Having copy and pasted that approach 3 times I realised something had to change so I implemented his 4th suggestion and wrote a Custom Binding. FWIW here is that custom binding
/// <summary>
/// Custom Binding for UIActivityIndicator Hidden.
/// This binding will ensure the indicator animates when shown and stops when hidden
/// </summary>
public class ActivityIndicatorViewHiddenTargetBinding : MvxConvertingTargetBinding
{
/// <summary>
/// Initializes a new instance of the <see cref="ActivityIndicatorViewHiddenTargetBinding"/> class.
/// </summary>
/// <param name="target">The target.</param>
public ActivityIndicatorViewHiddenTargetBinding(UIActivityIndicatorView target)
: base(target)
{
if (target == null)
{
MvxBindingTrace.Trace(
MvxTraceLevel.Error,
"Error - UIActivityIndicatorView is null in ActivityIndicatorViewHiddenTargetBinding");
}
}
/// <summary>
/// Gets the default binding mode.
/// </summary>
/// <value>
/// The default mode.
/// </value>
public override MvxBindingMode DefaultMode
{
get { return MvxBindingMode.OneWay; }
}
/// <summary>
/// Gets the type of the target.
/// </summary>
/// <value>
/// The type of the target.
/// </value>
public override System.Type TargetType
{
get { return typeof(bool); }
}
/// <summary>
/// Gets the view.
/// </summary>
/// <value>
/// The view.
/// </value>
protected UIActivityIndicatorView View
{
get { return Target as UIActivityIndicatorView; }
}
/// <summary>
/// Sets the value.
/// </summary>
/// <param name="target">The target.</param>
/// <param name="value">The value.</param>
protected override void SetValueImpl(object target, object value)
{
var view = (UIActivityIndicatorView)target;
if (view == null)
{
return;
}
view.Hidden = (bool)value;
if (view.Hidden)
{
view.StopAnimating();
}
else
{
view.StartAnimating();
}
}
}
I know that the way the declaration of the graphics device is suppose to look in the main Game1() constructor is:
GraphicsDeviceManager graphics;
graphics = new GraphicsDeviceManager(this);
then you can use stuff like:
graphics.PreferredBackBufferWidth = 1366;
But if I declare the same inside a separate class, what do I fill in for "this"?
GraphicsDeviceManager graphics;
graphics = new GraphicsDeviceManager(?);
EDIT:
After modifying everything as you said, I now get a error that sends me to this line of code:
/// <summary>
/// Event handler for when the Play Game menu entry is selected.
/// </summary>
void PlayGameMenuEntrySelected(object sender, PlayerIndexEventArgs e, Game game)
{
LoadingScreen.Load(ScreenManager, true, e.PlayerIndex,
new GameplayScreen(game));
}
This program is the menu sample you can get from Microsoft, heavily modified of course, this is the code that executes when you hit enter on the main menu screen with "Play Game" highlighted. I guess the problem is passing the variable.
Edit 2:
I fixed the code I think but now it sent me to this line and I don't know how to edit it.
playGameMenuEntry.Selected += PlayGameMenuEntrySelected;
You need a reference to your Game instance. I'd just pass it to your new class constructor.
For example, if you want to move the GraphicsDeviceManager instance into a separate class, Foo,
/// <summary>
/// This is the main type for your game
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
private Foo _foo;
public Game1()
{
_foo = new Foo(this);
}
protected override void Dispose(bool disposing)
{
if (_foo != null)
{
_foo.Dispose();
_foo = null;
}
base.Dispose(disposing);
}
}
public class Foo : IDisposable
{
private Game _game;
private GraphicsDeviceManager _graphics;
private SpriteBatch _spriteBatch;
public Foo(Game game)
{
_game = game;
_game.Content.RootDirectory = "Content";
_graphics = new GraphicsDeviceManager(_game);
_spriteBatch = new SpriteBatch(_game.GraphicsDevice);
}
public void Dispose()
{
if (!_spriteBatch.IsDisposed)
{
_spriteBatch.Dispose();
_spriteBatch = null;
}
}
}
Update
It's important to properly dispose of a lot of XNA objects (otherwise, you get memory leaks). See Foo.Dispose() and the Game1.Dispose() override above.
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