Is it possible to create a generic converter between enum values and ints?
I have a set of similar converters in my project:
Object^ SpecificEnumIntConverter::Convert(Object^ value, TypeName targetType, Object^ parameter, String^ language)
{
const auto asEnum = static_cast<SpecificEnumType>(value);
const auto asInt = static_cast<int>(asEnum);
return asInt;
}
Object^ SpecificEnumIntConverter::ConvertBack(Object^ value, TypeName targetType, Object^ parameter, String^ language)
{
const auto asInt = static_cast<int>(value);
const auto asEnum = static_cast<SpecificEnumType>(asInt);
return asEnum;
}
These converters are usually used to binding with ViewModel, i.e:
<ListBox SelectedIndex="{x:Bind VM.EnumTypeProperty, Mode=TwoWay, Converter={StaticResource SpecificEnumIntConverter}}">
I am wondering if it is possible to implement a generic converter.
Thanks in advance and best regards!
It is possible to define generic converter by using template<...> somehow.
public enum class EnumType1 : int
{
Foo1, Bar1
};
public enum class EnumType2 : int
{
Foo2, Bar2
};
// C++/CLI's syntax "generic<...> public ref class ... " cannot be used in C++/CX.
template <class _T>
ref class GenericEnumConverter : Windows::UI::Xaml::Data::IValueConverter
{
public:
virtual Platform::Object ^ Convert(Platform::Object ^value, Windows::UI::Xaml::Interop::TypeName targetType, Platform::Object ^parameter, Platform::String ^language)
{
const auto asEnum = static_cast<_T>(value);
const auto asInt = static_cast<int>(asEnum);
return asInt;
}
virtual Platform::Object ^ ConvertBack(Platform::Object ^value, Windows::UI::Xaml::Interop::TypeName targetType, Platform::Object ^parameter, Platform::String ^language)
{
const auto asInt = static_cast<int>(value);
const auto asEnum = static_cast<_T>(asInt);
return asEnum;
}
};
However it's not possible to lay such converters directly in Resources via XAML (because templated-class in C++/CX cannot take "public" accessibility-modifier). So you need to deploy them in code behind.
[Windows::Foundation::Metadata::WebHostHidden]
public ref class MainPage sealed
{
public:
MainPage()
{
Resources->Insert(L"EnumType1Converter", ref new GenericEnumConverter<EnumType1>());
Resources->Insert(L"EnumType2Converter", ref new GenericEnumConverter<EnumType2>());
InitializeComponent();
}
property EnumType1 Value1 {
EnumType1 get() { return m_Value1; }
void set(EnumType1 value) {
m_Value1 = value;
//OutputDebugString((m_Value1.ToString() + L"\r")->Begin());
}
}
property EnumType2 Value2 {
EnumType2 get() { return m_Value2; }
void set(EnumType2 value) {
m_Value2 = value;
//OutputDebugString((m_Value2.ToString() + L"\r")->Begin());
}
}
private:
EnumType1 m_Value1 = EnumType1::Foo1;
EnumType2 m_Value2 = EnumType2::Bar2;
};
Then you can use the converters in binding.
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel>
<ListBox SelectedIndex="{x:Bind Value1, Converter={StaticResource EnumType1Converter}, Mode=TwoWay}" Margin="16">
<ListBoxItem>Foo1</ListBoxItem>
<ListBoxItem>Bar1</ListBoxItem>
</ListBox>
<ListBox SelectedIndex="{x:Bind Value2, Converter={StaticResource EnumType2Converter}, Mode=TwoWay}" Margin="16">
<ListBoxItem>Foo2</ListBoxItem>
<ListBoxItem>Bar2</ListBoxItem>
</ListBox>
</StackPanel>
</Grid>
There is a workaround below for you to try:
Use a variable typeFullName to save the enum type for later use in ConvertBack method to determine the type of the target enum class.
public ref class SpecificEnumIntConverter sealed : Windows::UI::Xaml::Data::IValueConverter
{
private:
Platform::String^ typeFullName;
public:
virtual Platform::Object^ Convert(Platform::Object^ value, Windows::UI::Xaml::Interop::TypeName targetType,
Platform::Object^ parameter, Platform::String^ language)
{
typeFullName = value->GetType()->FullName;
EnumType1 enum1 = EnumType1::red;
EnumType2 enum2 = EnumType2::red;
if (typeFullName == enum1.GetType()->FullName)
{
const auto asEnum = static_cast<EnumType1>(value);
const auto asInt = static_cast<int>(asEnum);
return asInt;
}
else if(typeFullName == enum2.GetType()->FullName)
{
const auto asEnum = static_cast<EnumType2>(value);
const auto asInt = static_cast<int>(asEnum);
return asInt;
}
}
// No need to implement converting back on a one-way binding
virtual Platform::Object^ ConvertBack(Platform::Object^ value, Windows::UI::Xaml::Interop::TypeName targetType,
Platform::Object^ parameter, Platform::String^ language)
{
if (typeFullName != nullptr)
{
EnumType1 enum1 = EnumType1::red;
EnumType2 enum2 = EnumType2::red;
if (typeFullName == enum1.GetType()->FullName)
{
const auto asInt = static_cast<int>(value);
const auto asEnum = static_cast<EnumType1>(asInt);
return asEnum;
}
else if (typeFullName == enum2.GetType()->FullName)
{
const auto asInt = static_cast<int>(value);
const auto asEnum = static_cast<EnumType2>(asInt);
return asEnum;
}
}
}
};
EnumType1 and EnumType2 are the sample enum types, red is the member within the enum class.
Related
I have a class named VerseRangeReference that has the properties Chapter, FirstVerse and LastVerse.
I have decorated it with a TypeConverterAttribute [TypeConverter(typeof(VerseRangeReferenceConverter))]
I have an action on a controller like this
public Task<ViewResult> Verses(VerseRangeReference[] verses)
But the value of verses is always a single element with the value null. Here is my type converter
public class VerseRangeReferenceConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value == null)
throw new ArgumentNullException(nameof(value));
if (value.GetType() == typeof(string))
{
string source = (string)value;
return VerseRangeReference.ParseMultiple(source);
}
return null;
}
}
The result of VerseRangeReference.ParseMultiple(source) is a valid array of instances of VerseRange.
I had to implement a custom model binder. If someone can think of a way to do this with a TypeConverter then I will accept that answer instead because model binders are more complicated.
public class VerseRangeReferenceArrayModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
string modelName = bindingContext.ModelName;
ValueProviderResult valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
if (valueProviderResult != ValueProviderResult.None)
{
VerseRangeReference[] verseRangeReferences = VerseRangeReference.ParseMultiple(valueProviderResult.FirstValue);
bindingContext.Result = ModelBindingResult.Success(verseRangeReferences);
}
return Task.CompletedTask;
}
}
public class VerseRangerReferenceArrayModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType == typeof(VerseRangeReference[]))
return new BinderTypeModelBinder(typeof(VerseRangeReferenceArrayModelBinder));
return null;
}
}
This must be registered.
services.AddMvc(options =>
{
options.ModelBinderProviders.Insert(0, new VerseRangerReferenceArrayModelBinderProvider());
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
You can use a type converter to bind a comma separated string to a sequence of values. However, the type converter should convert from the string to the sequence directly. This means that the type converter should be configured for something like IEnumerable<T> or T[]. To simplify matters I will continue my explanation for IEnumerable<int> but if you want to use arrays instead you should just make sure that the type converter converts to an array instead of something that implements IEnumerable<T>.
You can configure a type converter for IEnumerable<int> using TypeDescriptor.AddAttributes:
TypeDescriptor.AddAttributes(
typeof(IEnumerable<int>),
new TypeConverterAttribute(typeof(EnumerableIntTypeConverter)));
This configures EnumerableIntTypeConverter as a type converter that can convert IEnumerable<int>.
This call has to be made when the process starts and in the case of ASP.NET Core this can conveniently be done in the Startup.Configure method.
Here is the EnumerableIntTypeConverter that converts the comma separated string of numbers to a list of ints:
internal class EnumerableIntTypeConverter : TypeConverter
{
private const char Separator = ',';
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
=> sourceType == typeof(string);
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (!(value is string #string))
throw new NotSupportedException($"{GetType().Name} cannot convert from {(value != null ? value.GetType().FullName : "(null)")}.");
if (#string.Length == 0)
return Enumerable.Empty<int>();
var numbers = new List<int>();
var start = 0;
var end = GetEnd(#string, start);
while (true)
{
if (!int.TryParse(
#string.AsSpan(start, end - start),
NumberStyles.AllowLeadingSign,
culture,
out var number))
throw new FormatException($"{GetType().Name} cannot parse string with invalid format.");
numbers.Add(number);
if (end == #string.Length)
break;
start = end + 1;
end = GetEnd(#string, start);
}
return numbers;
}
private static int GetEnd(string #string, int start)
{
var end = #string.IndexOf(Separator, start);
return end >= 0 ? end : #string.Length;
}
}
The parsing uses System.Memory to avoid allocating a new string for each number in the list. If your framework doesn't have the int.TryParse overload that accepts a Span<char> you can use string.Substring instead.
I am trying to set a delegate that takes a local variable as a parameter. The declaration looks like this:
ref class Main
{
private:
Func<String^>^ _expensiveMethodDelegate;
public:
property Func<String^>^ ExpensiveMethodDelegate
{
Func<String^>^ get() { return this->_expensiveMethodDelegate; }
void set(Func<String^>^ value) { this->_expensiveMethodDelegate = value; }
};
void DoWork()
{
String^ result = this->_expensiveMethodDelegate();
Debug::WriteLine(result);
}
};
In C# the code will look like this:
string parameter = "value";
Main main = new Main();
main.ExpensiveMethodDelegate = () =>
{
Thread.Sleep(1000); // do expensive work
return parameter + "1";
};
main.DoWork();
What is the easiest way of achieving this goal using managed C++ (VS 2015)? Note: I read articles Workaround for not having lambdas that can capture managed variables and Lambda expressions as CLR (.NET) delegates / event handlers in Visual C++ 2010 and still cannot figure out what is the solution.
I tried code like this (using make_delegate from the second article), but it fails to compile:
String^ parameter = L"value";
Main^ main = gcnew Main();
main->ExpensiveMethodDelegate = make_delegate(
[](String^ parameter) -> String^
{
Threading::Thread::Sleep(1000); // do work
return parameter + L"1";
});
main->DoWork();
This is what I came up with:
#pragma once
#include <new>
using namespace std::tr1;
using namespace System;
namespace helper
{
private struct return_type_helper
{
private:
template<class D>
struct dependent_false { enum { value = false }; };
template <class D>
struct illegal_delegate_type
{
static_assert(dependent_false<D>::value, "Delegates with more than 2 parameters, or with parameters of tracking reference types (T%), are not supported.");
};
struct anything
{
template<class T>
operator T() const;
};
public:
template<class D>
static decltype(static_cast<D^>(nullptr)(anything())) dummy(int(*)[2]);
template<class D>
static decltype(static_cast<D^>(nullptr)(anything(), anything())) dummy(int(*)[3]);
};
template<class Func, class Aligner = char, bool Match = (alignment_of<Func>::value == alignment_of<Aligner>::value)>
struct aligner
{
static_assert(Match, "Function object has unsupported alignment");
};
template<class Func, class Aligner>
private struct aligner<Func, Aligner, true>
{
typedef Aligner type;
};
template<class F>
private ref class lambda_wrapper
{
public:
lambda_wrapper(const F& f)
{
pin_ptr<F> pf = (interior_ptr<F>)&f_storage;
new(pf) F(f);
}
~lambda_wrapper()
{
pin_ptr<F> pf = (interior_ptr<F>)&f_storage;
pf->~F();
}
template <class D>
operator D ^ ()
{
D^ d = nullptr;
return gcnew D(this, &lambda_wrapper<F>::invoke<decltype(return_type_helper::dummy<D>(0))>);
}
private:
template<class T>
[System::Runtime::InteropServices::StructLayout(System::Runtime::InteropServices::LayoutKind::Sequential, Size = sizeof(T))]
value struct embedded_storage
{
private:
typename aligner<T>::type dummy;
};
embedded_storage<F> f_storage;
template<class R, class A1>
R invoke(A1 a1)
{
pin_ptr<F> pf = (interior_ptr<F>)&f_storage;
return (*pf)(a1);
}
template<class R, class A1, class A2>
R invoke(A1 a1, A2 a2)
{
pin_ptr<F> pf = (interior_ptr<F>)&f_storage;
return (*pf)(a1, a2);
}
};
template <typename...>
ref class DelegateHelper;
template<class TParam1, class TResult>
ref class DelegateHelper<TParam1, TResult>
{
private:
Func<TParam1, TResult>^ _lambda;
TParam1 _param1;
TResult Execute()
{
return this->_lambda(this->_param1);
}
public:
template<class TLambda>
DelegateHelper(TLambda lambda, TParam1 param1)
{
this->_lambda = gcnew helper::lambda_wrapper<TLambda>(lambda);
this->_param1 = param1;
}
static operator Func<TResult> ^ (DelegateHelper<TParam1, TResult>^ value)
{
return gcnew Func<TResult>(value, &DelegateHelper<TParam1, TResult>::Execute);
}
};
template<class TParam1, class TParam2, class TResult>
ref class DelegateHelper<TParam1, TParam2, TResult>
{
private:
Func<TParam1, TParam2, TResult>^ _lambda;
TParam1 _param1;
TParam2 _param2;
TResult Execute()
{
return this->_lambda(this->_param1, this->_param2);
}
public:
template<class TLambda>
DelegateHelper(TLambda lambda, TParam1 param1, TParam2 param2)
{
this->_lambda = gcnew helper::lambda_wrapper<TLambda>(lambda);
this->_param1 = param1;
this->_param2 = param2;
}
static operator Func<TResult> ^ (DelegateHelper<TParam1, TParam2, TResult>^ value)
{
return gcnew Func<TResult>(value, &DelegateHelper<TParam1, TParam2, TResult>::Execute);
}
};
}
This is how to use it:
String^ parameter1 = L"value1";
String^ parameter2 = L"value2";
Main^ main = gcnew Main();
auto lambda1 = [](String^ parameter) -> String^
{
Threading::Thread::Sleep(1000);
return parameter;
};
main->ExpensiveMethodDelegate = gcnew helper::DelegateHelper<String^, String^>(lambda1, parameter1);
main->DoWork();
auto lambda2 = [](String^ parameter1, String^ parameter2) -> String^
{
Threading::Thread::Sleep(1000);
return parameter1 + parameter2;
};
main->ExpensiveMethodDelegate = gcnew helper::DelegateHelper<String^, String^, String^>(lambda2, parameter1, parameter2);
main->DoWork();
Not sure if it is the most elegant way, but it does the work I was looking for.
My question is, that how can I specify a custom object as a role in a model derived from QAbstractListModel so when visualizing it within a ListView I can access its member variables. To have an example here is some simple code example:
This is my class representing my custom object:
class MyCustomObject {
public:
MyCustomObject(Qstring name, Qstring type);
QString getName();
QString getType();
private:
QString name;
QString type;
};
This is how the overridden data() function looks like now (but it is not working) of my MyModel derived from QAbsractListModel:
QVariant MyModel::data(const QModelIndex &index, int role) const {
if (index.row() < 0 || index.row() > m_atoms.count()) {
//if (!index.isValid()) {
return QVariant();
}
const MyData &data = m_data[index.row()];
if(role == SomeRole) {
return data.someString()
}
else if (role == MyCustomRole) {
return data.myCustomObject; // How can I do this?
}
return QVariant();
}
Here I specify the role names in MyModel:
QHash<int, QByteArray> AtomModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[SomeRole] = "someRole";
roles[MyCustomRole] = "myCustomRole";
return roles;
}
and this is how my ListView looks like in QML code with an example how I would like to access MyCustomObject member variables in the delegate:
ListView {
width: 400
height: 400
model: myModel
delegate: Text {
text: "Type: " + myCustomRole.getType() + ", Name: " + myCustomRole.getName() + ", some string: " someRole
}
}
EDIT1: => fix needed copy constructor
When I am adding Q_DECLARE_METATYPE under my MyCustomObject I receive the following error:
call to implicitly-deleted copy constructor of `MyCustomObject`
in instantiation of member function 'QtMetaTypePrivate::QMetaTypeFunctionHelper<MyCustomObject, true>::Construct' requested here
in instantiation of function template specialization 'qRegisterNormalizedMetaType<MyCustomObject>' requested here QtMetaTypePrivate::QMetaTypeFunctionHelper<T>::Construct,
return qRegisterNormalizedMetaType<T>(normalizedTypeName, dummy, defined);
in instantiation of function template specialization 'qRegisterMetaType<MyCustomObject>' requested here
Q_DECLARE_METATYPE(MyCustomObject)
expanded from macro 'Q_DECLARE_METATYPE'
#define Q_DECLARE_METATYPE(TYPE) Q_DECLARE_METATYPE_IMPL(TYPE)
expanded from macro 'Q_DECLARE_METATYPE_IMPL'
const int newId = qRegisterMetaType< TYPE >(#TYPE,
copy constructor of 'MyCustomObject' is implicitly deleted because base class 'QObject' has a deleted copy constructor
class MyCustomObject : public QObject
'QObject' has been explicitly marked deleted here Q_DISABLE_COPY(QObject)
expanded from macro 'Q_DISABLE_COPY'
Class(const Class &) Q_DECL_EQ_DELETE;\
EDIT2:
So I have added all the necessary functions what #Evgeny has suggested. My code now compiles without errors, but I get a qml error on run time saying:
TypeError: Property 'getType' of object QVariant(MyCustomObject) is not a function
I have added Q_INVOKABLE in front of the getType() method and I also deriving MyCustomObject class from public QObject. I have added Q_DECLARE_METATYPE at the bottom of my MyCustomObject header file. In the constructor of MyCustomObject I call qRegisterMetaType<MyCustomObject>("MyCustomObject") and in my main I register the class also like this qmlRegisterType<MyCustomObject>("com.test.mycustomobject", 1, 0, "MyCustomObject")
This is how MyCustomObject class looks like now:
class MyCustomObject : public QObject {
public:
MyCustomObject();
MyCustomObject(Qstring name, Qstring type);
MyCustomObject(const MyCustomObject& obj);
~MyCustomObject();
Q_INVOKABLE QString getName();
Q_INVOKABLE QString getType();
private:
QString name;
QString type;
};
Q_DECLARE_METATYPE(MyCustomObject)
This is how the overridden data() function looks like now of my MyModel derived from QAbsractListModel:
QVariant MyModel::data(const QModelIndex &index, int role) const {
if (index.row() < 0 || index.row() > m_atoms.count()) {
//if (!index.isValid()) {
return QVariant();
}
const MyData &data = m_data[index.row()];
if(role == SomeRole) {
return data.someString()
}
else if (role == MyCustomRole) {
QVariant var; // this is the part, which has changed
var.setValue(data.myCustomObject);
return var;
}
return QVariant();
}
All other functions which I have posted originally are the same.
First of all you need to declare your custom object for Qt metatype system. You should use Q_DECLARE_METATYPE macro for this. Also you may need to use qRegisterMetaType function. Then you should register your object to use it with QML. You should use qmlRegisterType function for that.
Also make sure you use Q_INVOKABLE for your objects methods.
I have a binding of the following form in XAML,
Title="{Binding SelectedNewsItems[0].Title}"
Note that it refers to a particular element in the SelectedNewsItems which is an ObservableCollection. (I have a collection of nine buttons of various sizes, each styled, and sized differently and so a more standard ListView is not appropriate.)
When I reassign SelectedNewsItems I raise a PropertyChanged event for SelectedNewsItems, however, this does not appear to cause the bindings to update for the individual bound elements and their properties. I have tried the following,
public ObservableCollection<NewsItem> _selectedNewsItems;
public ObservableCollection<NewsItem> SelectedNewsItems
{
get
{
return this._selectedNewsItems;
}
set
{
if (this._selectedNewsItems != value)
{
this._selectedNewsItems = value;
this.NotifyPropertyChanged();
for (int i = 0; i < this._selectedNewsItems.Count; i++)
{
this.NotifyPropertyChanged(String.Format("SelectedNewsItems[{0}].Content", i));
this.NotifyPropertyChanged(String.Format("SelectedNewsItems[{0}].Title", i));
this.NotifyPropertyChanged(String.Format("SelectedNewsItems[{0}].Id", i));
this.NotifyPropertyChanged(String.Format("SelectedNewsItems[{0}].Image", i));
}
}
}
}
Hmm, I cannot exacly say where your code is wrong (as I see only part of it), but maybe you haven't set your DataContex or something else. For the purpose of research I've made simple example, which works quite fine. Take a look at it and maybe it will help you:
In Xaml:
<Button x:Name="first" VerticalAlignment="Top" Content="{Binding SelectedNewsItems[0].Name}" Grid.Row="0"/>
<Button x:Name="second" VerticalAlignment="Center" Content="{Binding SelectedNewsItems[1].Name}" Grid.Row="1"/>
In code behind (I put all the code - yeah quite a lot of, but I cannot guess what is wrong with your code):
public class NewsItem
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
}
public partial class MainPage : PhoneApplicationPage, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaiseProperty(string property = null)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(property));
}
private ObservableCollection<NewsItem> _selectedNewsItems = new ObservableCollection<NewsItem>();
public ObservableCollection<NewsItem> SelectedNewsItems
{
get
{
return this._selectedNewsItems;
}
set
{
if (this._selectedNewsItems != value)
{
this._selectedNewsItems = value;
this.RaiseProperty();
for (int i = 0; i < this._selectedNewsItems.Count; i++)
{
this.RaiseProperty(String.Format("SelectedNewsItems[{0}].Name", i));
}
}
}
}
public MainPage()
{
NewsItem firstT = new NewsItem() { Name = "First" };
NewsItem secondT = new NewsItem() { Name = "Second" };
SelectedNewsItems.Add(firstT);
SelectedNewsItems.Add(secondT);
InitializeComponent();
this.DataContext = this;
first.Click += first_Click;
second.Click += second_Click;
}
private void first_Click(object sender, RoutedEventArgs e)
{
NewsItem change = new NewsItem() { Name = "Changed by First" };
SelectedNewsItems[1] = change;
}
private void second_Click(object sender, RoutedEventArgs e)
{
NewsItem change = new NewsItem() { Name = "Changed by Second" };
SelectedNewsItems[0] = change;
}
}
As I click on buttons the bindigs work, so maybe it will help you.
I am trying to create a custom Pushpin for Bing Maps in my WinRT application. My problem is that I need a reference to the actual Map from my page in order to pin the icons correctly in my userControl. So for example this is my DataTemplate which gets bound to the map and works fine for the normal pushpins. For my custom userControl to position correctly I need a reference to the parent Map in the userControl.
This is my XAML:
<m:MapItemsControl x:Name="Pushpinss" ItemsSource="{Binding InventoryItems}">
<m:MapItemsControl.ItemTemplate>
<DataTemplate>
<!-- NORMAL PUSHPIN WORKS -->
<m:Pushpin>
<m:MapLayer.Position>
<m:Location Latitude="{Binding WarehouseLatitude}"
Longitude="{Binding WarehouseLongitude}" />
</m:MapLayer.Position>
</m:Pushpin>
<!-- CUSTOM CONTROL DISPLAYS BUT DOES NOT POSITION CORRECTLY BECAUSE I NEED A REFERENCE TO THE MAP-->
<View:GPSIcon Latitude="{Binding WarehouseLatitude}"
Longitude="{Binding WarehouseLongitude}"
Radius="100000"/>
<x:Arguments>
</x:Arguments>
</DataTemplate>
</m:MapItemsControl.ItemTemplate>
</m:MapItemsControl>
This is my custom control:
public sealed partial class GPSIcon : UserControl
{
private Map _map;
private const double EARTH_RADIUS_METERS = 6378137;
public GPSIcon(Map map)
{
this.InitializeComponent();
_map = map;
_map.ViewChanged += (s, e) =>
{
UpdateAccuracyCircle();
};
}
public static readonly DependencyProperty LatitudeProperty =
DependencyProperty.Register("Latitude", typeof(double), typeof(GPSIcon), new PropertyMetadata(0));
public static readonly DependencyProperty LongitudeProperty =
DependencyProperty.Register("Longitude", typeof(double), typeof(GPSIcon), new PropertyMetadata(0));
public static readonly DependencyProperty RadiusProperty =
DependencyProperty.Register("Radius", typeof(double), typeof(GPSIcon), new PropertyMetadata(0));
public double Latitude
{
get { return (double)GetValue(LatitudeProperty); }
set { SetValue(LatitudeProperty, value); }
}
public double Longitude
{
get { return (double)GetValue(LongitudeProperty); }
set { SetValue(LongitudeProperty, value); }
}
/// <summary>
/// Radius in Metres
/// </summary>
public double Radius
{
get { return (double)GetValue(RadiusProperty); }
set
{
SetValue(RadiusProperty, value);
UpdateAccuracyCircle();
}
}
private void UpdateAccuracyCircle()
{
if (_map != null && Radius >= 0)
{
double groundResolution = Math.Cos(_map.Center.Latitude * Math.PI / 180) * 2 * Math.PI * EARTH_RADIUS_METERS / (256 * Math.Pow(2, _map.ZoomLevel));
double pixelRadius = Radius / groundResolution;
AccuracyCircle.Width = pixelRadius;
AccuracyCircle.Height = pixelRadius;
AccuracyCircle.Margin = new Thickness(-pixelRadius / 2, -pixelRadius / 2, 0, 0);
}
}
}
Is this possible at all? I have also tried using the x:Arguments directive as described here:
http://msdn.microsoft.com/en-us/library/ee795382.aspx
Thanks
UPDATE 1
Do following changes
1) Add empty constructor.
public GPSIcon()
{
this.InitializeComponent();
}
2) Declare DP of type Map
public Map MyMap
{
get { return (Map)GetValue(MyMapProperty); }
set { SetValue(MyMapProperty, value); }
}
public static readonly DependencyProperty MyMapProperty =
DependencyProperty.Register("MyMap", typeof(Map), typeof(GPSIcon), new PropertyMetadata(default(Map), OnMapSet));
private static void OnMapSet(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
_map = ((GPSIcon)(d)).MyMap;
_map.ViewChanged += (ss, ee) =>
{
((GPSIcon)(d)).UpdateAccuracyCircle();
};
}
3) Pass Map object like this in XAML
<m:Map x:Name="objMap">
<m:MapItemsControl x:Name="Pushpinss" ItemsSource="{Binding InventoryItems}">
<m:MapItemsControl.ItemTemplate>
<DataTemplate>
<View:GPSIcon Latitude="{Binding WarehouseLatitude}"
Longitude="{Binding WarehouseLongitude}"
Radius="100000"
MyMap="{Binding ElementName=objMap}"/>
</DataTemplate>
</m:MapItemsControl.ItemTemplate>
</m:MapItemsControl>
</m:Map>
Declare one more dependency property of type Map and then you should pass current map instance as a value of that DP in <View:GPSIcon ... />
Simply, you need to follow the same logic as how to Pass parameter to constructor from xaml in Silverlight
To get your custom UIElement to position properly on the map what you can do instead of doing this in code is simply set the position of the UIElement the same way you set the position of a pushpin.
For example:
<View:GPSIcon Radius="100000">
<m:MapLayer.Position>
<m:Location Latitude="{Binding WarehouseLatitude}"
Longitude="{Binding WarehouseLongitude}" />
</m:MapLayer.Position>
</View:GPSIcon>