4

Given a ArcGISLocalFeatureLayer as a layer on the WPF runtime map object, how can I bind a ViewModel to the selected feature or graphic?

<esri:ArcGISLocalFeatureLayer x:Name="MySampleLayer" ID="MyFeatures" 
Path="C:/..../Samples.mpk" LayerName="MyFeatures" OutFields="*" DisableClientCaching="True" 
Editable="True" AutoSave="False"  ValidateEdits="True" 
Mode="OnDemand"  SelectionColor="#FFFFFF00" />

I realize there is already a FeatureDataGrid and FeatureDataForm in the runtime, but I'm trying to achieve a little different user experience. For simplicity, suppose I just have some controls on the same window as the map:

<Grid >
    <StackPanel>
        <StackPanel.DataContext>
            <local:FeaturePropertiesViewModel/>
        </StackPanel.DataContext>
        <TextBox Text="{Binding FeatureName}" />
        <TextBox Text="{Binding FeatureInfo}" />
    </StackPanel>
</Grid>

and a simple ViewModel ...

class FeaturePropertiesViewModel  
{
    public string FeatureName { get; set; }
    public string FeatureInfo { get; set; }

    public Graphic SelectedFeature { get; set; }     // ??? use this somehow?
    public GraphicCollection AllGraphics { get; set; }  // ??? use this somehow? 
}

How do I get those text fields updated to when a new feature is selected? Ideally, I'd like two-way binding so that changing the text updates the underlying feature class.

Update: Based on Darlene's suggestion, I started looking into other ways to get the information include the mouse interactions - since that is how the feature is being selected.

I can capture/handle the MouseLeftButtonDown event and do everything I need to in the code-behind. So I started working of that to refactor to MVVM. Using MvvmLight, I can map that event to a command like this:

<esri:ArcGISLocalFeatureLayer ... />

            <i:Interaction.Triggers>
                <i:EventTrigger EventName="MouseLeftButtonDown">
                    <cmd:EventToCommand Command="{Binding Path=MyLeftMouseCommand, 
                     Source={StaticResource MyVieModel}}" 
                     CommandParameter="{Binding  RelativeSource={RelativeSource AncestorType=esriLocal:ArcGISLocalFeatureLayer}}"
                     PassEventArgsToCommand="False" />
                </i:EventTrigger>
            </i:Interaction.Triggers>
</esriLocal:ArcGISLocalFeatureLayer>

However, I need both the mouse event args (GraphicMouseButtonEventArgs) and the selected graphic. And I'm having trouble getting both passed to a command. I tried RelayCommand like this ...

public RelayCommand<ArcGISLocalFeatureLayer> MyLeftMouseCommand{ get; protected set; }

In constructor ...

    MyLeftMouseCommand= new RelayCommand<ArcGISLocalFeatureLayer>(p =>
    {
        Console.Writeline("It works!");
    });

And that will get me the CommandParameter value if I set PassEventArgsToCommand="False", or I can get GraphicMouseButtonEventArgs if I set PassEventArgsToCommand="True", but I can't get them at the same time!

Also using ReactiveUI if there is anything in there that might be helpful.

  • Well it looks like the behavior with PassEventArgsToCommand is expected behavior... stackoverflow.com/questions/9101957/… – Darlene Jul 1 '13 at 14:54
  • Thanks! I'm looking at your code and trying to digest it all to see if/how I can integrate it. – Keith G Jul 2 '13 at 15:32
1

This is a good question. I believe that the answer to your question is that there is not an easy way to do this. I think what you are hoping for (and what I would like to work) is

<esri:ArcGISLocalFeatureLayer SelectedGraphic={Binding SelectedFeature}.../>

Unfortunately, there is not anything like this that I know of. The layer (graphics or feature) has a SelectedGraphics collection, but that only has a getter so you cannot bind to an ObservableCollection on your view model of selected graphics. Have you posted this on the ArcGIS Runtime Forums?

How is the feature or graphic getting selected? I think whatever logic you have written that selects the graphic will have to raise some kind of event and explicitly set the ViewModel.SelectedFeature (or vice versa - raise an event and explicitly call Graphic.Select when SelectedFeature changes).

Fody's PropertyChanged can do a lot of this for you. If the FeatureName and FeatureInfo depend on the value of the SelectedFeature you can look at the DependsOn or AlsoNotifyFor attributes.

If you want to do extra processing when a property changes (such as updating the stored feature) you could use On_PropertyName_Changed.

[ImplementPropertyChanged]
public class FeaturePropertiesViewModel  
{
    [DependsOn("SelectedFeature")]
    public string FeatureName { get; set; }
    [DependsOn("SelectedFeature")]
    public string FeatureInfo { get; set; }

    public Graphic SelectedFeature { get; set; }     
    public GraphicCollection AllGraphics { get; set; }   

    public void OnFeatureInfoChanged(){
        //Update your feature
    }

    public void OnFeatureNameChanged(){
        //Update your feature
    }
}
  • I have posted on the ArcGIS forums. Still no solution. The feature is getting selected via left mouse click event on the layer. Then I'm just setting e.Graphic.Selected = true. Right now, this is in code-behind, but I want to move it to an ICommand instead. I can actually map the PropertyChanged method on the layer to an ICommand (whole different subject), but the command doesn't receive the sender as a parameter. The regular code-behind callback for layer.PropertyChanged does include the sender. My current workaround is using the code-behind callback, but I want MVVM and databinding. – Keith G Jun 28 '13 at 17:50
  • Have you looked at using MouseBindings to bind your mouse gesture to an ICommand (this will work with .Net 4, but not 3.5). msdn.microsoft.com/en-us/library/… – Darlene Jun 28 '13 at 20:19
  • Thanks for the suggestion. I've updated the question to reflect new progress. – Keith G Jun 28 '13 at 21:56
  • I still don't think there is a pure MVVM way to do what you're wanting. Since you always use the same ArcGIS Feature Layer maybe you don't need that as a command parameter and you can keep a reference to that layer in your ViewModel. Then your RelayCommand can take the MouseEventParameters or your view's code behind can just pass the x,y screen coordinates to a method in your ViewModel. There could be good reasons not to pass UI Event Args (such as MouseEventArgs) to your view model (easier unit testing). – Darlene Jul 1 '13 at 15:07
1

I posted to this at Esri's forums some ideas to this but to be short here is my approach for databinding issue since in real apps I don't define ViewModels as static resources in Views (usually my ViewModels are injected to Views by DI container + framework combination).

Use DataContextProxy for Wpf like described at http://kajanus.net/?p=15 (WinRT/SL version is blogged in here : http://kajanus.net/?p=387)

public class DataContextProxy : Freezable
{
    #region Overrides of Freezable

    protected override Freezable CreateInstanceCore()
    {
        return new DataContextProxy();
    }

    #endregion

    public object Data
    {
        get { return (object)GetValue(DataProperty); }
        set { SetValue(DataProperty, value); }
    }

    public static readonly DependencyProperty DataProperty =
        DependencyProperty.Register("Data", typeof(object), typeof(DataContextProxy), new UIPropertyMetadata(null));
}

In ViewModel you need to have collection and selected properties (like you already have) and then you can bind those as you want in your View.

The binding goes like this:

<Grid>
    <Grid.Resources>
       <local:DataContextProxy x:Key="proxy" Data="{Binding}" />
     </Grid.Resources>

    <esri:Map>
        <esri:ArcGISTiledMapServiceLayer Url="http://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer" /> 
        <esri:GraphicsLayer ID="MyGraphicsLayer"
                GraphicsSource="{Binding Path=Data.AllGrahics, Source={StaticResource proxy}}" />
    </esri:Map>

    <YourControl DataContext="{Binding Path=Data.SelectedGraphics, Source= {StaticResource proxy}}"/>
</Grid>

Then you can direct SelectedGraphic set to ViewModel from code-behind or from Xaml. In simpler cases I usually just invoke some ViewModel behavior from the code-behind. I keep this in View to encapsulate mouse controlling and not bloat the ViewModel for chancing event handlers from view. In more complex cases I have MapService (or MapController) class that encapsulates the Map behavior (and event handling mess) and this gets to injected to the ViewModel to make working with Map a bit easier.

To use SelectedGraphic property in View, you can just data bind that to another control like any other property and if you want, you can access attributes with {Binding Attributes[nameOfTheAttribute]} notation if the data context is set to SelectedGraphic in that scope.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.