Xamarin.Forms MVVM WebView not Loading - xamarin

We use the Xamarin.Forms WebView to display content of a certain object, but this content won't be displayed in the WebView until we rotate the Device twice.
The .xmal-File Code:
<WebView x:Name="WebView" HeightRequest="{Binding Height}" WidthRequest="{Binding Width}">
<WebView.Source>
<HtmlWebViewSource Html="{Binding Code}"/>
</WebView.Source>
</WebView>
And the ModelView:
private double _height;
private double _width;
public ICommand ItemTappedCommand { get; private set; }
public string SearchBarLabelText { get; private set; }
public object LastTappedItem { get; set; }
public int ColumnCount { get; private set; } = 2;
public string CodeName { get; private set; }
public string CodeContent { get; private set; }
public string Code { get; private set; }
public double Height
{
get => _height;
private set
{
if (_height == value)
return;
_height = value;
OnPropertyChanged(nameof(Height));
}
}
public double Width
{
get => _width;
private set
{
if (_width == value)
return;
_width = value;
OnPropertyChanged(nameof(Width));
}
}
public ErrorcodeDetailPageViewModel(Errorcode code)
{
Device.Info.PropertyChanged += DevicePropertyChanged;
DevicePropertyChanged(null, null);
Code = code.Content;
CodeName = code.Label;
CodeContent = code.Content;
}
private void DevicePropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (Device.Info.CurrentOrientation)
{
case DeviceOrientation.Portrait:
Height = Device.Info.PixelScreenSize.Height - 150; // 120 is the Height of the ControlTemplate used on this Page + the top row on uwp; only required in portrait mode
Width = Device.Info.PixelScreenSize.Width;
break;
case DeviceOrientation.Landscape:
Height = Device.Info.PixelScreenSize.Height - 50;
Width = Device.Info.PixelScreenSize.Width;
break;
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
So, als already described, if we rotate the Device twice the content will be displayed, otherwise it won't. I think the problem is related to our bindings...
I should mention that we have some kind ob banner/headline on top of the WebView.

Code should also have a backing notification so that the view is aware of when its value changes.
Like
private string code;
public string Code {
get => code;
private set {
if (code == value)
return;
code = value;
OnPropertyChanged(nameof(Code));
}
}
If you walk through the step of why you have to turn twice, you will see that is why you have to do that.
When the value is first set it is blank.
On first turn the value is set by the event handler not since no notification is sent to the view it is not visibly changed.
On second turn the value is already set and when the view redraws, the value is shown in the view.

Related

ItemsSource doesn't show/bind when ObservableCollection is filled before loading the page

I have a carouselview, in that view I have an ObservableCollection binded as an itemssource. I am able to bind the collection and it would show when I execute the viewmodel's command in the OnAppearing event.
Code that works:
Second Page
public partial class SecondPage : ContentPage
{
public Coll(bool hard, string subject)
{
InitializeComponent();
var vm = (DataSelectionViewModel)BindingContext;
vm.Hard = hard;
vm.Subject = subject;
/* had to set "hard" and "subject" here again, otherwise data won't load */
}
protected override async void OnAppearing()
{
var vm = (DataSelectionViewModel)BindingContext;
base.OnAppearing();
await vm.LoadData.ExecuteAsync().ConfigureAwait(false);
}
}
The viewmodel for second page
public class DataSelectionViewModel : BaseViewModel
{
private string subject;
public string Subject { get => subject; set => SetProperty(ref subject, value); }
private bool hard;
public bool Hard { get => hard; set => SetProperty(ref hard, value); }
public ObservableCollection<Items> FilteredData { get; set; }
public UserSelectionViewModel()
{
_dataStore = DependencyService.Get<IDataStore>();
LoadData= new AsyncAwaitBestPractices.MVVM.AsyncCommand(FilterData);
FilteredData = new ObservableCollection<Items>();
}
public async Task FilterData()
{
FilteredData.Clear();
var filtereddata = await _dataStore.SearchData(Hard, Subject).ConfigureAwait(false);
foreach (var data in filtereddata)
{
FilteredData.Add(data);
}
}
}
First Page where second page gets Hard and Subject values
private async void ButtonClick(object sender, EventArgs e)
{
var vm = (BaseViewModel)BindingContext;
vm.Hard = HardButtonSelected == Hard;
vm.Subject = vm.Subject.ToLower();
await Navigation.PushAsync(new SecondPage(vm.Hard, vm.Subject));
}
So I want to change my code so that if I press the button on the first page, data instantly starts to filter and add to the ObservableCollection and when it's finished, then navigate to the second page. However if I try to load it to the BaseViewModel and then get the data from the second viewmodel it won't show the data.
Code that doesn't work:
Second Page
public partial class SecondPage : ContentPage
{
public SecondPage()
{
InitializeComponent();
}
}
The viewmodel for second page
public class DataSelectionViewModel : BaseViewModel
{
public ObservableCollection<Items> FilteredData { get; set; }
public UserSelectionViewModel()
{
FilteredData = new ObservableCollection<Items>();
}
}
BaseViewModel
public class BaseViewModel : INotifyPropertyChanged
{
private string subject;
public string Subject { get => subject; set => SetProperty(ref subject, value); }
private bool hard;
public bool Hard { get => hard; set => SetProperty(ref hard, value); }
public ObservableCollection<Items> FilteredData { get; set; }
/* BaseViewModel has implementation of SetProperty */
}
First Page where second page gets Hard and Subject values
private async void ButtonClick(object sender, EventArgs e)
{
var vm = (BaseViewModel)BindingContext;
vm.Hard = HardButtonSelected == Hard;
vm.Subject = vm.Subject.ToLower();
}
First Page viewmodel
public class FirstPageViewModel : BaseViewModel
{
public IAsyncCommand MehetButtonClickedCommand { get; }
readonly IPageService pageService;
readonly IFeladatokStore _feladatokStore;
public FeladatValasztoViewModel()
{
_dataStore = DependencyService.Get<IDataStore>();
ButtonClickedCommand = new AsyncCommand(ButtonClicked);
pageService = DependencyService.Get<IPageService>();
}
private async Task ButtonClicked()
{
await FilterData();
await pageService.PushAsync(new SecondPage());
}
private async Task FilterData()
{
FilteredData.Clear();
var datas = await _dataStore.SearchData(Subject, Hard).ConfigureAwait(false);
foreach (var data in datas)
{
FilteredData.Add(data);
}
}
So basically this gives a null exception error. I also tried giving the ObservableCollection as an argument for SecondPage(ObservableCollection x) and that did work, but because I had to make another ObservableCollection for it and copy from one to another it stopped being async and froze for a couple of seconds. So my question is how can I make this async?
To avoid delay, build the new collection in a private variable. Then set the property to that variable:
// Constructor with parameter
public SomeClass(IList<Items> data)
{
SetFilteredDataCopy(data);
}
public ObservableCollection<Items> FilteredData { get; set; }
private void SetFilteredDataCopy(IList<Items> src)
{
var copy = new ObservableCollection<Items>();
foreach (var item in src)
copy.Add(item);
FilteredData = copy;
//MAYBE OnPropertyChanged(nameof(FilteredData));
}

xamarin forms dynamic image tapgesture recognizer control

How can I provide control (change) of my dynamic images created in xamarin forms by clicking?
Image ggImage = new Image()
ggImage.Source = otoTip.imagex
ggImage.AutomationId = "seat_" +otoTip.num
ggImage.WidthRequest = imageWidth
ggImage.HeightRequest = 50
ggImage.VerticalOptions = LayoutOptions.Start
ggImage.HorizontalOptions = LayoutOptions.Start
ggImage.GestureRecognizers.Add(tapGestureRecognizer)
For example, I just want to change a resume source.
You have to implement a TapGestureRecognizer and then add it to your image...
Image ggImage = new Image();
ggImage.Source = otoTip.imagex
ggImage.AutomationId = "seat_" +otoTip.num
ggImage.WidthRequest = imageWidth
ggImage.HeightRequest = 50;
ggImage.VerticalOptions = LayoutOptions.Start;
ggImage.HorizontalOptions = LayoutOptions.Start;
// your TapGestureRecognizer implementation, this is just a sample...
var tapGestureRecognizer = new TapGestureRecognizer();
tapGestureRecognizer.Tapped += (s, e) => { ggImage.Source = yourNewSource };
ggImage.GestureRecognizers.Add(tapGestureRecognizer);
In order to change every unique image of your dynamically created images you could extend your OtoTip class and
public class OtoTip : INotifyPropertyChanged
{
public int x { get; set; }
public int y { get; set; }
public int num { get; set; }
public int near { get; set; }
public int status { get; set; }
public int yy { get; set; }
public int xx { get; set; }
// xx
private string _imagex;
public string imagex
{
get { return _imagex; }
set
{
_imagex = value;
OnPropertyChanged(nameof(imagex));
}
}
private string _imageActivex;
public string imageActivex
{
get { return _imageActivex; }
set
{
_imageActivex = value;
OnPropertyChanged(nameof(imageActivex));
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
You have to extend your getVoyagesData() now, to also set the imageActivex like your standard image.
Now you have to adjust the TapGestureRecognizer to use the new property
tapGestureRecognizer.Tapped += (s, e) =>
{
if(ggImage.Source = otoTip.imagex) {
ggImage.Source = otoTip.imageActivex;
}
else
{
ggImage.Source = otoTip.imagex;
}
};
Does this help?

Adding Left Border line in itextsharp

I was facing issue to work on rowspan but with some code in C#, I was able to achieve that. Currently I have the data what I needed.
How can I draw left border line for the first column so that it looks correct.
here is what I have now.
I need to add left border line.
here is my code:
{
ReportTableCell cell = null;
// prevent null values from crashing the system (always a good idea)
if (text == null)
{
text = string.Empty;
}
// else already valid
ReportTable.CreateReportTableCell(text, out cell);
cell.RowSpan = row_span;
cell.ColumnSpan = column_span;
cell.Style.WrapText = true;
cell.Style.BorderWidth = border_width;
// cell.Style.BorderColor = CellBorderColor.Gray;
if ( repeated)
{
// cell.Style.Border = BOTTOM_BORDER;
cell.Style.BorderWidth = border_width;
}
else
{
cell.Style.BorderWidth = 0;
repeated = true;
}
cell.Style.Font.FontStyle = font_style;
cell.Style.Font.FontSize = font_size;
cell.Style.BackgroundColor = background_color;
cell.Style.IsColumnHeader = is_column_header;
cell.Style.HorizontalAlignment = horizontal_alignment;
if (is_column_header)
{
cell.Style.VerticalAlignment = VerticalCellAlignment.Middle;
}
else
{
cell.Style.VerticalAlignment = VerticalCellAlignment.Top;
}
repeated = true;
return cell;
}
based on the condition i.e.
if ( repeated){}
I am removing border, which I know it has removed border around 4 corners, how can i append an extra line only for the left side.
UPDATE : 1
after using the code as below :
if ( repeated)
{
cell.Style.BorderWidth = border_width;
}
else
{
cell.Style.BorderWidth = 0;
cell.Style.Border = BOTTOM_BORDER | LEFT_BORDER;
repeated = true;
}
changed nothing!.
Here is the base class of the report.
public class ReportTableCell
{
public static readonly int ALL_BORDER;
public static readonly int BOTTOM_BORDER;
public static readonly int LEFT_BORDER;
public static readonly int NO_BORDER;
public static readonly int RIGHT_BORDER;
public static readonly int TOP_BORDER;
public bool AllowBlanks { get; set; }
public int ColumnSpan { get; set; }
public ReportFont Font { get; set; }
public int RowSpan { get; set; }
public TableCellStyle Style { get; set; }
public ReportTable Table { get; set; }
public object Value { get; set; }
public void AddImage(string FileName);
}
UPDATE : 2
I have changed the code as you suggested but , I just have to add single border line to the last. this is what i achieved till now :

Xamarin, Both ListView and RecyclerView, click one item, another one selected

I'm having a trouble with both ListView and RecyclerView
Initially, I created a ListView, everything is fine. Then I set onClick event for it so that every time I click an item, it changes its color to yellow. The OnClick function I wrote in the MainActivity. Problem is that when I test, not only that item changes its color but 2 items change. I read that it's because I reuse the view.
So I switch my tactics, using RecyclerView instead but same problem occurs. When I click one item to change its color, another below also changes. I guess it's because both ListView and RecyclerView reuse those Item so they confuse when I click one.
I don't know how to solve this problem, I found a solution is to add an array of boolean which marks which item is clicked but it doesn't work. Any idea guys?
So here is the code
MainActivity
class MainActivity : Activity
{
public RecyclerView recyclerView;
public RecyclerView.LayoutManager manager;
public RecyclerView.Adapter adapter;
List<Row> lst;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
init();
recyclerView = (RecyclerView)FindViewById(Resource.Id.recyclerView);
manager = new LinearLayoutManager(this);
recyclerView.SetLayoutManager(manager);
CustomAdapter adapter = new CustomAdapter(lst, this);
adapter.ItemClick += onItemClick;
recyclerView.SetAdapter(adapter);
}
public void init()
{
lst = new List<Row>();
for (int i = 0; i < 15; i++)
{
Row row = new Row() { field1="1:43:00", field2="09-Apr-16", field3="KPI/Overflow", field4="Kevin Bacon", field5="Unowned", field6= "People Counting # IPCAM-ID-C-1-1" };
lst.Add(row);
}
}
public void onItemClick(object sender, int position)
{
int itemPos = position + 1;
//Toast.MakeText(this, "this is " + itemPos, ToastLength.Short).Show();
recyclerView.GetChildAt(position).SetBackgroundColor(Android.Graphics.Color.Green);
}
}
Custom adapter
public class CustomAdapter : RecyclerView.Adapter
{
public Activity _activity;
public List<Row> lst;
public event EventHandler<int> ItemClick;
public CustomAdapter(List<Row> lst, Activity activity)
{
this.lst = lst;
this._activity = activity;
}
public override int ItemCount
{
get
{
return lst.Count;
}
}
public void OnClick(int position)
{
if (ItemClick!=null)
{
ItemClick(this, position);
}
}
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
MyViewHolder myholder = holder as MyViewHolder;
myholder.textView1.Text = lst[position].field1;
myholder.textView2.Text = lst[position].field2;
myholder.textView3.Text = lst[position].field3;
myholder.textView4.Text = lst[position].field4;
myholder.textView5.Text = lst[position].field5;
myholder.textView6.Text = lst[position].field6;
}
public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
{
View v = this._activity.LayoutInflater.Inflate(Resource.Layout.item, parent, false);
TextView tv1 = (TextView)v.FindViewById(Resource.Id.textView1);
TextView tv2 = (TextView)v.FindViewById(Resource.Id.textView2);
TextView tv3 = (TextView)v.FindViewById(Resource.Id.textView3);
TextView tv4 = (TextView)v.FindViewById(Resource.Id.textView4);
TextView tv5 = (TextView)v.FindViewById(Resource.Id.textView5);
TextView tv6 = (TextView)v.FindViewById(Resource.Id.textView6);
MyViewHolder holder = new MyViewHolder(v, OnClick) { textView1 = tv1, textView2 = tv2, textView3 = tv3, textView4 = tv4, textView5 = tv5, textView6 = tv6 };
return holder;
}
}
class MyViewHolder : RecyclerView.ViewHolder
{
public TextView textView1, textView2, textView3, textView4, textView5, textView6;
public View mainView;
public MyViewHolder(View view, Action<int> listener) : base(view)
{
mainView = view;
mainView.Click += (sender, e) => listener(base.Position);
}
}
I followed the example for the OnClick handler on Xamarin site
https://developer.xamarin.com/guides/android/user_interface/recyclerview/
Your issue is with your code. You send the correct position to your event handler, but then you increment it by one in the Activity. Both ends should be using the 0-based index of the item position. There is no need to increment by one.
For changing the background color of the selected item, you can use a selector in XML so you wouldn't even need to do this in code.
Here is an example.
row_selector.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true" android:color="#android:color/green" />
<item android:state_selected="false" android:color="#android:color/transparent"/>
</selector>
row_content.axml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/row_layout_parent"
android:background="#drawable/row_selector">
<!-- your row content -->
</LinearLayout>
Then your view holder would be updated to this...
class MyViewHolder : RecyclerView.ViewHolder
{
public TextView textView1, textView2, textView3, textView4, textView5, textView6;
public View mainView;
private LinearLayout _layoutParent;
public MyViewHolder(View view, Action<int> listener) : base(view)
{
mainView = view;
_layoutParent = mainView.FindViewById<LinearLayout>(Resource.Id.row_layout_parent);
_layoutParent.Click += (sender, e) => _layoutParent.Selected = true;
}
}
I removed the other click event. If you still need it for other reasons, then you can add it back, but it's not necessary for just setting the item background color when selected.
For Listview you should set choiceMode as below.
listView.ChoiceMode = ChoiceMode.Single;
Hope it help you :)-
Create a reusable recycleview adapter GENERIC
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.Graphics;
using Android.OS;
using Android.Runtime;
using Android.Support.V7.App;
using Android.Support.V7.Widget;
using Android.Text;
using Android.Text.Style;
using Android.Util;
using Android.Views;
using Android.Widget;
using Java.Util.Zip;
using ActionMenuView = Android.Support.V7.Widget.ActionMenuView;
namespace Android.Basic.Core
{
public class GenericRecyclerViewAdapter<T> : RecyclerView.Adapter
{
/// <summary>
/// You can set this for different custom cardview
/// </summary>
private int CardViewResourceLayout { get; set; }
public ObservableCollection<T> Items { get; private set; }
public event EventHandler<RecyclerViewViewHolder> ItemViewTemplated;
public RecyclerView.LayoutManager layoutManager;
public GenericRecyclerViewAdapter(RecyclerView recyclerView, IEnumerable<T> items, int cardViewResourceLayout, bool isList = true, bool isVertical = true) : base()
{
if(isList)
{
var vertical = isVertical ? LinearLayoutManager.Vertical : LinearLayoutManager.Horizontal;
layoutManager = new LinearLayoutManager(recyclerView.Context, vertical, false);
}
else
{
var vertical = isVertical ? GridLayoutManager.Vertical : GridLayoutManager.Horizontal;
layoutManager = new GridLayoutManager(recyclerView.Context, 3, vertical, false);
}
recyclerView.SetLayoutManager(layoutManager);
this.Items = new ObservableCollection<T>(items);
this.CardViewResourceLayout = cardViewResourceLayout;
this.Items.CollectionChanged += delegate
{
this.NotifyDataSetChanged();
};
}
public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
{
var itemView = LayoutInflater.From(parent.Context).Inflate(CardViewResourceLayout, parent, false);
#if DEBUG
Log.Info("GenericRecyclerViewAdapter - ", CardViewResourceLayout.ToString());
#endif
RecyclerViewViewHolder vh = new RecyclerViewViewHolder(itemView);
return vh;
}
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
RecyclerViewViewHolder vh = holder as RecyclerViewViewHolder;
vh.ItemPosition = position;
vh.TemplateView.Tag = position;
vh.TemplateView.Click -= TemplateView_Click;
vh.TemplateView.Click += TemplateView_Click;
ItemViewTemplated?.Invoke(this, vh);
}
public event EventHandler<T> ItemClicked;
private void TemplateView_Click(object sender, EventArgs e)
{
var position = (int)((View)sender).Tag;
this.ItemClicked?.Invoke(sender, this.Items[position]);
}
public override int ItemCount
{
get { return this.Items.Count; }
}
public override long GetItemId(int position)
{
return base.GetItemId(position);
}
}
public class RecyclerViewViewHolder : RecyclerView.ViewHolder, View.IOnCreateContextMenuListener,
IMenuItemOnMenuItemClickListener
{
public View TemplateView { get; private set; }
public int ItemPosition { get; set; }
public event EventHandler<MenuInfo> ContextMenuCreated;
public event EventHandler<object> MenuItemClicked;
public MenuInfo MenuInfo { get; private set; }
public object Data { get; set; }
public RecyclerViewViewHolder(View itemView) : base(itemView)
{
// Locate and cache view references:
this.TemplateView = itemView;
this.TemplateView.SetOnCreateContextMenuListener(this);
}
public void OnCreateContextMenu(IContextMenu menu, View v, IContextMenuContextMenuInfo menuInfo)
{
MenuInfo = new MenuInfo(menu, v, menuInfo);
ContextMenuCreated?.Invoke(this, MenuInfo);
}
private Android.Views.MenuInflater menuInflater = null;
/// <summary>
/// After ContextMenuCreated
/// </summary>
/// <param name="resourcemenu"></param>
public void InflateMenu(int resourcemenu, SpannableString titleColor = null, object dta = null)
{
if (dta != null)
this.Data = dta;
if (this.TemplateView.Context is AppCompatActivity activity)
{
menuInflater = activity.MenuInflater;
}
else if (this.TemplateView.Context is Activity activity2)
{
menuInflater = activity2.MenuInflater;
}
var contextMenu = this.MenuInfo.ContextMenu;
contextMenu.Clear();
menuInflater.Inflate(resourcemenu, contextMenu);
var num = contextMenu.Size() - 1;
for (int i = 0; i <= num; i++)
{
var men = contextMenu.GetItem(i);
if(titleColor != null)
{
if (i == 0)
{
men.SetTitle(titleColor);
men.SetChecked(true);
}
}
if (i != 0)
{
men.SetOnMenuItemClickListener(this);
}
}
}
public bool OnMenuItemClick(IMenuItem item)
{
this.MenuItemClicked?.Invoke(item, this.Data);
return true;
}
public float PosX;
public float PosY;
}
public class MenuInfo
{
public IContextMenu ContextMenu { get; }
public View View { get; }
public IContextMenuContextMenuInfo ContextMenuInfo { get; }
public MenuInfo(IContextMenu contextMenu, View view, IContextMenuContextMenuInfo menuInfo)
{
this.ContextMenu = contextMenu;
this.View = view;
this.ContextMenuInfo = menuInfo;
}
}
}
Usage
RecyclerView recyclerView = new RecyclerView(this);
var viewAdapter = new Android.Basic.Core.GenericRecyclerViewAdapter<Java.IO.File>(recyclerView, files, Resource.Layout.directory_item);
var indiColor = ThemeHelper.IsDark ? ColorHelper.GetRandomLightColor() : ColorHelper.GetRandomDarkColor();
viewAdapter.ItemViewTemplated += (dd, holder) =>
{
var file = files[holder.ItemPosition];
var view = holder.ItemView;
var expanded = view.FindViewById<ExpandedView>(Resource.Id.expandedView);
expanded.SetToggleColor(indiColor);
expanded.SetTitle(file.Name);
GenerateRecycler(expanded, file);
};
recyclerView.SetAdapter(viewAdapter);
expandedView.AddExpandedView(recyclerView);

WP7 Mock Microsoft.Devices.Sensors.Compass when using the emulator

I'd like to be able to simulate the compass sensor when running a Windows Phone 7.1 in the emulator.
At this stage I don't particularly care what data the compass returns. Just that I can run against something when using the emulator to test the code in question.
I'm aware that I could deploy to my dev unlocked phone to test compass functionality but I've found the connection via the Zune software to drop out frequently.
Update
I've looked into creating my own wrapper class that could simulate the compass when running a debug build and the compass isn't otherwise supported.
The Microsoft.Devices.Sensors.CompassReading struct has me a bit stumpted. Because it is a struct where the properties can only be set internally I can't inherit from it to provide my own values back. I looked at using reflection to brute force some values in but Silverlight doesn't appear to allow it.
as you already noticed I had a similar problem. when I mocked the compass sensor, I also had difficulties because you cannot inherite from the existing classes and write your own logic. Therefore I wrote my own compass interface which is the only compass functionality used by my application. Then there are two implementations, one wrapper to the WP7 compass functionalities and my mock compass.
I can show you some code, but not before weekend as I'm not at my delevopment machine atm.
Edit:
You already got it but for other people who have the same problem I'll add my code. As I already said, I wrote an interface and two implementations, one for the phone and a mock implementation.
Compass Interface
public interface ICompass
{
#region Methods
void Start();
void Stop();
#endregion
#region Properties
CompassData CurrentValue { get; }
bool IsDataValid { get; }
TimeSpan TimeBetweenUpdates { get; set; }
#endregion
#region Events
event EventHandler<CalibrationEventArgs> Calibrate;
event EventHandler<CompassDataChangedEventArgs> CurrentValueChanged;
#endregion
}
Used data classes and event args
public class CompassData
{
public CompassData(double headingAccurancy, double magneticHeading, Vector3 magnetometerReading, DateTimeOffset timestamp, double trueHeading)
{
HeadingAccuracy = headingAccurancy;
MagneticHeading = magneticHeading;
MagnetometerReading = magnetometerReading;
Timestamp = timestamp;
TrueHeading = trueHeading;
}
public CompassData(CompassReading compassReading)
{
HeadingAccuracy = compassReading.HeadingAccuracy;
MagneticHeading = compassReading.MagneticHeading;
MagnetometerReading = compassReading.MagnetometerReading;
Timestamp = compassReading.Timestamp;
TrueHeading = compassReading.TrueHeading;
}
#region Properties
public double HeadingAccuracy { get; private set; }
public double MagneticHeading { get; private set; }
public Vector3 MagnetometerReading { get; private set; }
public DateTimeOffset Timestamp { get; private set; }
public double TrueHeading { get; private set; }
#endregion
}
public class CompassDataChangedEventArgs : EventArgs
{
public CompassDataChangedEventArgs(CompassData compassData)
{
CompassData = compassData;
}
public CompassData CompassData { get; private set; }
}
WP7 implementation
public class DeviceCompass : ICompass
{
private Compass _compass;
#region Implementation of ICompass
public void Start()
{
if(_compass == null)
{
_compass = new Compass {TimeBetweenUpdates = TimeBetweenUpdates};
// get TimeBetweenUpdates because the device could have change it to another value
TimeBetweenUpdates = _compass.TimeBetweenUpdates;
// attach to events
_compass.CurrentValueChanged += CompassCurrentValueChanged;
_compass.Calibrate += CompassCalibrate;
}
_compass.Start();
}
public void Stop()
{
if(_compass != null)
{
_compass.Stop();
}
}
public CompassData CurrentValue
{
get { return _compass != null ? new CompassData(_compass.CurrentValue) : default(CompassData); }
}
public bool IsDataValid
{
get { return _compass != null ? _compass.IsDataValid : false; }
}
public TimeSpan TimeBetweenUpdates { get; set; }
public event EventHandler<CalibrationEventArgs> Calibrate;
public event EventHandler<CompassDataChangedEventArgs> CurrentValueChanged;
#endregion
#region Private methods
private void CompassCalibrate(object sender, CalibrationEventArgs e)
{
EventHandler<CalibrationEventArgs> calibrate = Calibrate;
if (calibrate != null)
{
calibrate(sender, e);
}
}
private void CompassCurrentValueChanged(object sender, SensorReadingEventArgs<CompassReading> e)
{
EventHandler<CompassDataChangedEventArgs> currentValueChanged = CurrentValueChanged;
if (currentValueChanged != null)
{
currentValueChanged(sender, new CompassDataChangedEventArgs(new CompassData(e.SensorReading)));
}
}
#endregion
}
Mock implementation
public class MockCompass : ICompass
{
private readonly Timer _timer;
private CompassData _currentValue;
private bool _isDataValid;
private TimeSpan _timeBetweenUpdates;
private bool _isStarted;
private readonly Random _random;
public MockCompass()
{
_random = new Random();
_timer = new Timer(TimerEllapsed, null, Timeout.Infinite, Timeout.Infinite);
_timeBetweenUpdates = new TimeSpan();
_currentValue = new CompassData(0, 0, new Vector3(), new DateTimeOffset(), 0);
}
#region Implementation of ICompass
public void Start()
{
_timer.Change(0, (int)TimeBetweenUpdates.TotalMilliseconds);
_isStarted = true;
}
public void Stop()
{
_isStarted = false;
_timer.Change(Timeout.Infinite, Timeout.Infinite);
_isDataValid = false;
}
public CompassData CurrentValue
{
get { return _currentValue; }
}
public bool IsDataValid
{
get { return _isDataValid; }
}
public TimeSpan TimeBetweenUpdates
{
get { return _timeBetweenUpdates; }
set
{
_timeBetweenUpdates = value;
if (_isStarted)
{
_timer.Change(0, (int) TimeBetweenUpdates.TotalMilliseconds);
}
}
}
public event EventHandler<CalibrationEventArgs> Calibrate;
public event EventHandler<CompassDataChangedEventArgs> CurrentValueChanged;
#endregion
#region Private methods
private void TimerEllapsed(object state)
{
_currentValue = new CompassData(_random.NextDouble()*5,
(_currentValue.MagneticHeading + 0.1)%360,
_currentValue.MagnetometerReading,
new DateTimeOffset(DateTime.UtcNow),
(_currentValue.TrueHeading + 0.1)%360);
_isDataValid = true;
EventHandler<CompassDataChangedEventArgs> currentValueChanged = CurrentValueChanged;
if(currentValueChanged != null)
{
currentValueChanged(this, new CompassDataChangedEventArgs(_currentValue));
}
}
#endregion
}

Resources