How to create a FadeTo() animation based on BindableProperty change, MVVM style? - c#

Let's say I have a view, with an <Image> element in it, that is visible based on whether it has a non-null property or not in the viewmodel. Like this:
<Image IsVisible="{Binding HasPhoto}" Source="{Binding Url}" />
With this ViewModel (only copied the relevant part):
private string url {get;set;}
public string Url {
get => url;
set {
url = value;
OnPropertyChanged(nameof(HasPhoto));
OnPropertyChanged();
}
}
public bool HasPhoto { get => Url != null; }
And somewhere in the ViewModel, I set: Url = null. This will cause HasPhoto to become false, and immediately render the <Image> element invisible, leaving no room to animate with .FadeTo(0) in the behavior. This is my current behavior implementation:
public class FadingBehavior : Behavior<VisualElement>
{
public uint FadeTime { get; set; } = 250;
protected override void OnAttachedTo(VisualElement bindable)
{
base.OnAttachedTo(bindable);
bindable.PropertyChanged += VisibilityMightHaveChanged;
}
protected override void OnDetachingFrom(VisualElement bindable)
{
base.OnDetachingFrom(bindable);
bindable.PropertyChanged -= VisibilityMightHaveChanged;
}
private void VisibilityMightHaveChanged(object sender, PropertyChangedEventArgs args)
{
var element = sender as VisualElement;
if (args.PropertyName == VisualElement.IsVisibleProperty.PropertyName)
{
var fadeToValue = element.IsVisible ? 1 : 0;
element.Opacity = -fadeToValue + 1;
element.FadeTo(fadeToValue, FadeTime);
}
}
}
This is fine, when the the <Image> becomes visible, because first it becomes visible, then it fades from 0 opacity to 1. But, for fading out this is not good, as the Url is removed instantly, rendering the image empty and invisible, so the fade-out animation cannot even start.
What is a good way to create an animation based on a bindable property value change?

I think that you are not far from a good solution.
What I would do is to inherit from Image and create FadableImage with bindable property called something like VisibilityHelper. Then when you assign the value to it the Image will still be visible and you can initiate the animation in a way that you already do. Just in the end when animation completes you should check the value of VisibilityHelper and if it is still the same also set IsVisible to the appropriate value.

Related

Attached Property for Binding to WebBrowser not working

I have been looking for a way to get the HTML out of a WPF WebBrowser control. The two best options I have found are to bind a customer attached property to the property in the application or to build a new control from the WebBrowser control. Considering my level of knowledge and the fact that (as of now I really only need this one time) I chose the first. I even considered breaking MVVM style and using code-behind but I decided not to give up in the binding.
I found several examples on creating the attached property, I finally chose this one, from here Here:
namespace CumminsInvoiceTool.HelperClasses
{
public static class WebBrowserExtentions
{
public static readonly DependencyProperty DocumentProperty =
DependencyProperty.RegisterAttached("Document", typeof(string), typeof(WebBrowserExtentions), new UIPropertyMetadata(null, DocumentPropertyChanged));
public static string GetDocument(DependencyObject element)
{
return (string)element.GetValue(DocumentProperty);
}
public static void SetDocument(DependencyObject element, string value)
{
element.SetValue(DocumentProperty, value);
}
public static void DocumentPropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
WebBrowser browser = target as WebBrowser;
if (browser != null)
{
string document = e.NewValue as string;
browser.NavigateToString(document);
}
}
}
}
I also added the following to the xaml for the WebBrowser control (I have tried both with and without the "Path=" in the xaml:
<WebBrowser local:WebBrowserExtentions.Document="{Binding Path=PageCode}" Source="https://www.cummins-distributors.com/"/>
My View has a tab control one tab has the WebBrowser control and another tab has a textbox. When I click the get code the viewModel runs a function to set property bound to the textbox to the string the attached property of the WebBrowser is bound to. Below is the code of my ViewModel.
namespace CumminsInvoiceTool.ViewModels
{
class ShellViewModel : Screen
{
private string _browserContent;
public string BrowserContent
{
get { return _browserContent; }
set {
_browserContent = value;
NotifyOfPropertyChange(() => BrowserContent);
}
}
private string _pageCode;
public string PageCode
{
get { return _pageCode; }
set {
_pageCode = value;
NotifyOfPropertyChange(() => PageCode);
}
}
public void StartProgressCommand()
{
}
public void GetContent()
{
if (!string.IsNullOrEmpty(PageCode))
{
BrowserContent = PageCode;
}
else
{
MessageBox.Show("There is no cintent to show", "No content Error", MessageBoxButton.OK);
}
}
}
}
The application compiles and runs but when I click "Get Code" I am getting the messagebox for "PageCode" is empty.
When I set a break point at the beginning of the function for the button, the PageCode string is showing "null".
Is this an issue because I am using Caliburn.Micro or am I missing something else?
------- EDIT for comments ----------
The button calls GetContent() in the "ShellViewModel" code above. I know the button is bound and working because the app is showing the custom messagebox I have set up to let me know when "pageCode" is null or empty.
The textbox looks like:
<TextBox x:Name="BrowserContent"/>

Does the Picker have a 'Focused' event to use as an event trigger?

I cannot get the picker to signal my MV when it gains focus.
I am trying to get a filtered list to populate a Picker through MVVM (after the user enters a string into a textbox to filter the list).
My setup is a screen that will have a textbox that can be used to enter a string (which will be used to filter the list). The picker is populated by querying a local db and returning that as a list. At first, I was rebuilding that list on every keystroke (by requerying the db). That worked, but the lag between keystrokes was unacceptable.
I tried to NOT build the list between keystrokes, but ONLY when the textbox lost focus. Finally was able to make that work by using an unfocused event trigger to tell the VM that the textbox was unfocused (done through MessagingCenter). At this time, I also decided to not query the db for the list, but to use an ObservableCollection, thinking that when the OC is filtered, it would automatically update the picker list.
This worked (kinda), but if you typed a string into the textbox and then clicked the picker, the picker list was populated BEFORE the unfocused event fired (I think). I only say that because the list was always one step behind. In other words, if you entered 'ABC' and clicked the picker, the list still had ALL items in the list. But if you went back to the textbox and replaced the string with 'XYZ' and clicked the picker again, the list populated with ONLY items that included 'ABC'. If you were to enter a string in the textbox, click on ANY other control (besides the picker), and then click the picker, the list would be correct.
So that got me to thinking that maybe the 'focused' event is firing before 'unfocused', and frankly, it makes more sense to build that list when the picker is selected anyway (since that's where it will be needed). However, when I move the trigger to the picker control, I get errors when the events are fired. The build is fine, and everything works until the picker gains control, when it then crashes.
FWIW, here is the code for the picker (with trigger):
<!-- (other code) -->
<Picker ItemsSource="{Binding myList}" Title="Select a specification" SelectedItem="{Binding mySelectedItem}" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="3">
<Picker.Triggers>
<EventTrigger Event="Focused">
<local:FocusedTriggerAction />
</EventTrigger>
</Picker.Triggers>
<Picker.ItemDisplayBinding>
<Binding Path="FullName"/>
</Picker.ItemDisplayBinding>
</Picker>
<!-- (other code) -->
The code for the 'FocusedTriggerAction':
namespace DatabaseTest
{
public class FocusedTriggerAction : TriggerAction<Entry>
{
protected override void Invoke(Entry entry)
{
MessagingCenter.Send<FocusedTriggerAction>(this, "changeList");
}
}
}
And the VM:
namespace DatabaseTest.ViewModels
{
public class MyViewModel : INotifyPropertyChanged
{
public MyViewModel()
{
List<MyOptions> mystuff = new List<MyOptions>();
using (SQLite.SQLiteConnection conn = new SQLite.SQLiteConnection(App.DB_PATH))
{
conn.CreateTable<MyOptions>();
mystuff = conn.Table<MyOptions>().ToList();
}
myList = new ObservableCollection<MyOptions>(mystuff);
MessagingCenter.Subscribe<FocusedTriggerAction> (this, "changeList", (sender) =>
{
if (mylookupstring == "")
testme = 1;
else
myList = new ObservableCollection<MyOptions>(mystuff.Where(o => o.SpecFullName.Contains(mylookupstring)));
});
}
string mylookupstring = string.Empty;
string myselectedthing;
MyOptions myselectedpickeritem;
int testme = 0;
int foundselecteditem = 0;
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public string myLookupString
{
get { return mylookupstring; }
set
{
mylookupstring = value;
mylookupstring = mylookupstring.ToUpper();
OnPropertyChanged();
}
}
private ObservableCollection<MyOptions> _mylist;
public ObservableCollection<MyOptions> myList
{
get { return _mylist; }
set
{
if (_mylist != value)
{
_mylist = value;
OnPropertyChanged();
}
}
}
//...Other code...
}
}
For anyone else that might need help with this issue, I figured out the issue with getting the Picker to trigger correctly (with the help of a fellow reader here). When I moved the trigger from the entry control to the picker, I forgot to change the 'entry' parts to 'picker' in the code-behind.
I had to change this:
namespace DatabaseTest
{
public class FocusedTriggerAction : TriggerAction<Entry>
{
protected override void Invoke(Entry entry)
{
MessagingCenter.Send<FocusedTriggerAction>(this, "changeList");
}
}
}
To this:
namespace DatabaseTest
{
public class FocusedTriggerAction : TriggerAction<Picker>
{
protected override void Invoke(Picker sender)
{
MessagingCenter.Send<FocusedTriggerAction>(this, "changeList");
}
}
}

Bind elements of List<string> to text blocks

I'm trying to make some sort of wheel spinning. I have 5 customized text blocks, text file with the list of values (it may consist of 1-1000 items). After reading the file I have a 'List fileValues' with its values. I decided to create another 'List wheel' which will contain up to 5 elements at the time and is expected to be bind to text blocks.
When one presses a spin button, last element of 'wheel' is removed and new element from 'values' is added to the beginning of the 'wheel' list.
In order UI will be responsive to changes in the list, it is good to bind each element in the 'wheel' to corresponding text block on UI. But what I tried to do up to this moment didn't work.
Here is what I tried to do (the code is a little bit dirty, but I try to make it work firstly).
5 customized text blocks
<TextBlock Name="Value1" TextWrapping="WrapWithOverflow"/>
<TextBlock Name="Value2" TextWrapping="WrapWithOverflow"/>
<TextBlock Name="Value3" TextWrapping="WrapWithOverflow"/>
<TextBlock Name="Value4" TextWrapping="WrapWithOverflow"/>
<TextBlock Name="Value5" TextWrapping="WrapWithOverflow"/>
ObservableList which implements INotifyCollectionChanged interface
class ObservableList : INotifyCollectionChanged, IEnumerable
{
private readonly List<string> _valuesList;
public string First
{
get { return _valuesList.First(); }
}
public string Last
{
get { return _valuesList.Last(); }
}
public ObservableList()
{
this._valuesList = new List<string>();
}
public string this[Int32 index]
{
get
{
if (_valuesList.Count == 0 || index + 1 > _valuesList.Count)
{
return "------";
}
return _valuesList[index];
}
}
public void AddLast(string value)
{
_valuesList.Add(value);
OnNotifyCollectionChanged();
}
public void AddFirst(string value)
{
_valuesList.Insert(0, value);
OnNotifyCollectionChanged();
}
public void RemoveFirst()
{
_valuesList.RemoveAt(0);
OnNotifyCollectionChanged();
}
public void RemoveLast()
{
_valuesList.Remove(_valuesList.Last());
OnNotifyCollectionChanged();
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
public void OnNotifyCollectionChanged()
{
if (CollectionChanged != null)
{
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
public IEnumerator GetEnumerator()
{
return (_valuesList as IEnumerable).GetEnumerator();
}
}
XAML Code-behind
public partial class MainWindow : Window
{
private List<string> _values = new List<string>();
private ObservableList _uiValues = new ObservableList();
public MainWindow()
{
InitializeComponent();
Value1.DataContext = _uiValues[0];
Value2.DataContext = _uiValues[1];
Value3.DataContext = _uiValues[2];
Value4.DataContext = _uiValues[3];
Value5.DataContext = _uiValues[4];
}
private void LoadFileBtn_Click(object sender, RoutedEventArgs e)
{
//Loads text file and fills _values
}
private void SpinBtn_Click(object sender, RoutedEventArgs e)
{
InitUiTextBlocks();
//Spin simulation
}
private void InitUiTextBlocks()
{
_uiValues.Clear();
for (int i = 0; i < 5; ++i)
{
//Nothing appears on UI and CollectionChanged event is null
_uiValues.AddLast(_values.First());
_values.RemoveAt(0);
}
}
}
I tried to use 'ObservableCollection', but the effect is the same. Nothing appears on UI. In fact I can't imagine how to bind each of List element to specific Label. Is it even possible to do such binding?
In the XAML do something like:
<Label Name="some_name" Content="{Binding SomeStingProperty}"/>
and in the code behind, have a
public string SomeStringProperty {get; set;}
you can bind to a collection as well, and if it's an ObservableCollection it will update on change.
search for basic XAML binding otherwise :)
(on a side note, it's cleaner i think it the XAML, i personally don't like to do it in the code behind ...)
As a side note, and totally self promoting, here are 2 articles that will probably help:
Understanding selected value
The big mvvm template.
The second might be a bit over your head if you're a beginner, but should be worth reading nevertheless.

WP8 change background image when button clicked

When a button is clicked, I want to change the background image in the mainpage. In the xaml I have ; Style="{StaticResource LayoutGridStyle}for background. How can I achieve this ? Thanks !
It can be done like this:
public static class FrameworkElementExtensions
{
public static object TryFindResource(this FrameworkElement element, object resourceKey)
{
var currentElement = element;
while (currentElement != null)
{
var resource = currentElement.Resources[resourceKey];
if (resource != null)
{
return resource;
}
currentElement = currentElement.Parent as FrameworkElement;
}
return Application.Current.Resources[resourceKey];
}
}
private void PageTitle_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
ApplicationTitle.Style = (Style)ApplicationTitle.TryFindResource("PhoneTextTitle1Style");
}
sample is given here
Include the style or background image (however you want to handle it) as a property in your ViewModel for the mainpage. Then bind to this...
Style="{Binding NameOFViewModelProperty}
The when you change the style through the button clicked code make sure you raise a property changed event...
RaisePropertyChanged("NameOFViewModelProperty");
so the page knows to refresh itself with the changes.

WPF bing maps control polylines/polygons not draw on first add to collection

I'm working on this surface project where we have a bing maps control and where we would like to draw polylines on the map, by using databinding.
The strange behaviour that's occuring is that when I click the Add button, nothing happens on the map. If I move the map little bit, the polyline is drawn on the map. Another scenario that kind of works, is click the add button once, nothing happens, click it again both polylines are drawn. (In my manual collection I have 4 LocationCollections) so the same happens for the 3rd click and the fourth click where again both lines are drawn.
I have totally no idea where to look anymore to fix this. I have tried subscribing to the Layoutupdated events, which occur in both cases. Also added a collectionchanged event to the observablecollection to see if the add is triggered, and yes it is triggered. Another thing I tried is changing the polyline to pushpin and take the first location from the collection of locations in the pipelineviewmodel, than it's working a expected.
I have uploaded a sample project for if you want to see yourself what's happening.
Really hope that someone can point me in the right direction, because i don't have a clue anymore.
Below you find the code that i have written:
I have the following viewmodels:
MainViewModel
public class MainViewModel
{
private ObservableCollection<PipelineViewModel> _pipelines;
public ObservableCollection<PipelineViewModel> Pipes
{
get { return _pipelines; }
}
public MainViewModel()
{
_pipelines = new ObservableCollection<PipelineViewModel>();
}
}
And the PipelineViewModel which has the collection of Locations which implements INotifyPropertyChanged:
PipelineViewModel
public class PipelineViewModel : ViewModelBase
{
private LocationCollection _locations;
public string Geometry { get; set; }
public string Label { get; set; }
public LocationCollection Locations
{
get { return _locations; }
set
{
_locations = value;
RaisePropertyChanged("Locations");
}
}
}
My XAML looks like below:
<s:SurfaceWindow x:Class="SurfaceApplication3.SurfaceWindow1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="http://schemas.microsoft.com/surface/2008"
xmlns:m="clr-namespace:Microsoft.Maps.MapControl.WPF;assembly=Microsoft.Maps.MapControl.WPF"
Title="SurfaceApplication3">
<s:SurfaceWindow.Resources>
<DataTemplate x:Key="Poly">
<m:MapPolyline Locations="{Binding Locations}" Stroke="Black" StrokeThickness="5" />
</DataTemplate>
</s:SurfaceWindow.Resources>
<Grid>
<m:Map ZoomLevel="8" Center="52.332074,5.542302" Name="Map">
<m:MapItemsControl Name="x" ItemsSource="{Binding Pipes}" ItemTemplate="{StaticResource Poly}" />
</m:Map>
<Button Name="add" Width="100" Height="50" Content="Add" Click="add_Click"></Button>
</Grid>
</s:SurfaceWindow>
And in our codebehind we are setting up the binding and the click event like this:
private int _counter = 0;
private string[] geoLines;
private MainViewModel _mainViewModel = new MainViewModel();
/// <summary>
/// Default constructor.
/// </summary>
public SurfaceWindow1()
{
InitializeComponent();
// Add handlers for window availability events
AddWindowAvailabilityHandlers();
this.DataContext = _mainViewModel;
geoLines = new string[4]{ "52.588032,5.979309; 52.491143,6.020508; 52.397391,5.929871; 52.269838,5.957336; 52.224435,5.696411; 52.071065,5.740356",
"52.539614,4.902649; 52.429222,4.801025; 52.308479,4.86145; 52.246301,4.669189; 52.217704,4.836731; 52.313516,5.048218",
"51.840869,4.394531; 51.8731,4.866943; 51.99841,5.122375; 52.178985,5.438232; 51.8731,5.701904; 52.071065,6.421509",
"51.633362,4.111633; 51.923943,6.193542; 52.561325,5.28717; 52.561325,6.25946; 51.524125,5.427246; 51.937492,5.28717" };
}
private void add_Click(object sender, RoutedEventArgs e)
{
PipelineViewModel plv = new PipelineViewModel();
plv.Locations = AddLinestring(geoLines[_counter]);
plv.Geometry = geoLines[_counter];
_mainViewModel.Pipes.Add(plv);
_counter++;
}
private LocationCollection AddLinestring(string shapegeo)
{
LocationCollection shapeCollection = new LocationCollection();
string[] lines = Regex.Split(shapegeo, ";");
foreach (string line in lines)
{
string[] pts = Regex.Split(line, ",");
double lon = double.Parse(pts[1], new CultureInfo("en-GB"));
double lat = double.Parse(pts[0], new CultureInfo("en-GB"));
shapeCollection.Add(new Location(lat, lon));
}
return shapeCollection;
}
I did some digging on this problem and found that there is a bug in the Map implementation. I also made a workaround for it which can be used like this
<m:Map ...>
<m:MapItemsControl Name="x"
behaviors:MapFixBehavior.FixUpdate="True"/>
</m:Map>
I included this fix in your sample application and uploaded it here: SurfaceApplication3.zip
The visual tree for each ContentPresenter looks like this
When you add a new item to the collection the Polygon gets the wrong Points initially. Instead of values like 59, 29 it gets something like 0.0009, 0.00044.
The points are calculated in MeasureOverride in MapShapeBase and the part that does the calculation looks like this
MapMath.TryLocationToViewportPoint(ref this._NormalizedMercatorToViewport, location, out point2);
Initially, _NormalizedMercatorToViewport will have its default values (everything is set to 0) so the calculations goes all wrong. _NormalizedMercatorToViewport gets set in the method SetView which is called from MeasureOverride in MapLayer.
MeasureOverride in MapLayer has the following two if statements.
if ((element is ContentPresenter) && (VisualTreeHelper.GetChildrenCount(element) > 0))
{
child.SetView(...)
}
This comes out as false because the ContentPresenter hasn't got a visual child yet, it is still being generated. This is the problem.
The second one looks like this
IProjectable projectable2 = element as IProjectable;
if (projectable2 != null)
{
projectable2.SetView(...);
}
This comes out as false as well because the element, which is a ContentPresenter, doesn't implement IProjectable. This is implemented by the child MapShapeBase and once again, this child hasn't been generated yet.
So, SetView never gets called and _NormalizedMercatorToViewport in MapShapeBase will have its default values and the calculations goes wrong the first time when you add a new item.
Workaround
To workaround this problem we need to force a re-measure of the MapLayer. This has to be done when a new ContentPresenter is added to the MapItemsControl but after the ContentPresenter has a visual child.
One way to force an update is to create an attached property which has the metadata-flags AffectsRender, AffectsArrange and AffectsMeasure set to true. Then we just change the value of this property everytime we want to do the update.
Here is an attached behavior which does this. Use it like this
<m:Map ...>
<m:MapItemsControl Name="x"
behaviors:MapFixBehavior.FixUpdate="True"/>
</m:Map>
MapFixBehavior
public class MapFixBehavior
{
public static DependencyProperty FixUpdateProperty =
DependencyProperty.RegisterAttached("FixUpdate",
typeof(bool),
typeof(MapFixBehavior),
new FrameworkPropertyMetadata(false,
OnFixUpdateChanged));
public static bool GetFixUpdate(DependencyObject mapItemsControl)
{
return (bool)mapItemsControl.GetValue(FixUpdateProperty);
}
public static void SetFixUpdate(DependencyObject mapItemsControl, bool value)
{
mapItemsControl.SetValue(FixUpdateProperty, value);
}
private static void OnFixUpdateChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
MapItemsControl mapItemsControl = target as MapItemsControl;
ItemsChangedEventHandler itemsChangedEventHandler = null;
itemsChangedEventHandler = (object sender, ItemsChangedEventArgs ea) =>
{
if (ea.Action == NotifyCollectionChangedAction.Add)
{
EventHandler statusChanged = null;
statusChanged = new EventHandler(delegate
{
if (mapItemsControl.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
mapItemsControl.ItemContainerGenerator.StatusChanged -= statusChanged;
int index = ea.Position.Index + ea.Position.Offset;
ContentPresenter contentPresenter =
mapItemsControl.ItemContainerGenerator.ContainerFromIndex(index) as ContentPresenter;
if (VisualTreeHelper.GetChildrenCount(contentPresenter) == 1)
{
MapLayer mapLayer = GetVisualParent<MapLayer>(mapItemsControl);
mapLayer.ForceMeasure();
}
else
{
EventHandler layoutUpdated = null;
layoutUpdated = new EventHandler(delegate
{
if (VisualTreeHelper.GetChildrenCount(contentPresenter) == 1)
{
contentPresenter.LayoutUpdated -= layoutUpdated;
MapLayer mapLayer = GetVisualParent<MapLayer>(mapItemsControl);
mapLayer.ForceMeasure();
}
});
contentPresenter.LayoutUpdated += layoutUpdated;
}
}
});
mapItemsControl.ItemContainerGenerator.StatusChanged += statusChanged;
}
};
mapItemsControl.ItemContainerGenerator.ItemsChanged += itemsChangedEventHandler;
}
private static T GetVisualParent<T>(object childObject) where T : Visual
{
DependencyObject child = childObject as DependencyObject;
while ((child != null) && !(child is T))
{
child = VisualTreeHelper.GetParent(child);
}
return child as T;
}
}
MapLayerExtensions
public static class MapLayerExtensions
{
private static DependencyProperty ForceMeasureProperty =
DependencyProperty.RegisterAttached("ForceMeasure",
typeof(int),
typeof(MapLayerExtensions),
new FrameworkPropertyMetadata(0,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsMeasure));
private static int GetForceMeasure(DependencyObject mapLayer)
{
return (int)mapLayer.GetValue(ForceMeasureProperty);
}
private static void SetForceMeasure(DependencyObject mapLayer, int value)
{
mapLayer.SetValue(ForceMeasureProperty, value);
}
public static void ForceMeasure(this MapLayer mapLayer)
{
SetForceMeasure(mapLayer, GetForceMeasure(mapLayer) + 1);
}
}

Categories

Resources