Select to view content in your preferred language

User controls - code structuring

1579
12
10-20-2010 11:38 AM
Ann_KristinEkeberg
Emerging Contributor
In my organization we are going to use the same user controls in multiple applications. All these user controls interact with the map. One example is a user control to display a layer list (TOC). The user control is bound to the map element. I would like to structure my code and have all these user controls in a separate project. The result is dll files that I can use in the various applications.

The challenge:
The user control has been tested and works fine when all the code is in the MainPage.xaml. When it is pulled out to a separate project I have noticed the following: The user controls in the page get loaded before the map control. (The map control is placed on top of the xaml page.) This means that the user control gets a null value for the map element on startup. If I place a refresh button inside the user control and clicks it after the map has been loaded, the user control gets the data it should.

Has anyone seen this? Any ideas how to solve it?
0 Kudos
12 Replies
JenniferNery
Esri Regular Contributor
Are you using Binding?

I would do something like this:
In MainPage.xaml:
<local:WidgetControl x:Name="MyWidget" DataContext="{Binding ElementName=MyMap}"/>


In my WidgetControl (or in your case the UserControl responsible for TOC):
<ComboBox x:Name="MyComboBox" ItemsSource="{Binding Layers}">
 <ComboBox.ItemTemplate>
  <DataTemplate>
   <TextBlock Text="{Binding ID}"/>
  </DataTemplate>
 </ComboBox.ItemTemplate>
</ComboBox>


If MyComboBox existed in MainPage, I would normally write:
ItemsSource="{Binding ElementName=MyMap, Path= Layers}"

But if I have set the DataContext of the UserControl, and the two elements do not exist in the same page, I can change it to and know that Binding will still be resolved.
ItemsSource="{Binding Layers}"
0 Kudos
Ann_KristinEkeberg
Emerging Contributor
Thank you, Jennifer, for replying.

I am using binding the way that you outlined.

The problem that I am experiencing is that the user control get loaded before the map control. When I do a debug I see the following order of events:
1. The user control is being loaded/initialized before the map is displayed in the application
2. The Map object from the binding to the usercontrol is null
3. The user control is loaded and empty (since it did not get any layers from the map)
4. Then the map is shown.

After that the map control is available and the layerlist (or other user controls) can be refreshed and populated using a refresh function I wrote.

My Visual Studio solution is set up like this:
Project 1: MyMapApp - with the mainpage.xaml
Project 2: MyMapApp.Web - with the xap and all needed for deployment
Project 3: MyMapControls - with my user controls (that I am going to use in many map apps)

🙂
0 Kudos
DarinaTchountcheva
Frequent Contributor
Hi Ann,

One way to acheive this could be using Dependancy Property in your UserControl:

#region Dependancy Properties

        protected static readonly DependencyProperty MapProperty=
            DependencyProperty.Register("Map", typeof(Map), typeof(YourUserControlName), new PropertyMetadata(OnMapPropertyChanged));

        public Map Map
        {
            get { return (Map)this.GetValue(MapProperty); }
            set
            {
                this.SetValue(MapProperty, (DependencyObject)value);                
            }
        }

        private static void OnMapPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            (d as YourUserControlName).PopulateLayerList();
        }

        private void PopulateLayerList()
        {           
            if (this.Map != null)
            {                                      
                //populate your layer list here
            }
        }

        #endregion       


Then your UserControl declaration in xaml will be:

[HTML]
<local:WidgetControl x:Name="MyWidget" Map="{Binding ElementName=MyMap}"/>
[/HTML]

I haven't tested it your scenario and there might be a better approach, but it might be worth trying.

Good Luck!
0 Kudos
Ann_KristinEkeberg
Emerging Contributor
Hi Ann,

One way to acheive this could be using Dependancy Property in your UserControl:

#region Dependancy Properties

        protected static readonly DependencyProperty MapProperty=
            DependencyProperty.Register("Map", typeof(Map), typeof(YourUserControlName), new PropertyMetadata(OnMapPropertyChanged));

        public Map Map
        {
            get { return (Map)this.GetValue(MapProperty); }
            set
            {
                this.SetValue(MapProperty, (DependencyObject)value);                
            }
        }

        private static void OnMapPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            (d as YourUserControlName).PopulateLayerList();
        }

        private void PopulateLayerList()
        {           
            if (this.Map != null)
            {                                      
                //populate your layer list here
            }
        }

        #endregion       


Then your UserControl declaration in xaml will be:

[HTML]
<local:WidgetControl x:Name="MyWidget" Map="{Binding ElementName=MyMap}"/>
[/HTML]

I haven't tested it your scenario and there might be a better approach, but it might be worth trying.

Good Luck!


Thank you. I did find an error in my setup of the DependencyProperty. Thanks again.
0 Kudos
SergioC
Emerging Contributor
Hello -
I am sort of using the same principles in using User Controls to develop my application. I am having an issues setting up a  Legend user control and binding the map service. I can get the legend dialogue box to show up, but no layers are loaded. Seems like the user control is not recognizing the Map Object from my main.xaml page. I was able to create a Address Locator User Control without  any problems, but I can get this one work properly. Seems like the application is never refreshed to load the layers to the legend.
If I move the legend code out of the user control and to the mainpage, it works, but I would like to keep everything separated using user controls.  Any suggestions are greatly appreciated.

My LegendDialog.xaml  User control contains:

<Border Background="#77919191" BorderThickness="1" CornerRadius="5"
            HorizontalAlignment="Right"  VerticalAlignment="Top"
            Margin="20" Padding="5" BorderBrush="Black" >
            <esri:Legend Map="{Binding ElementName=MyMap}" 
                         LayerIDs="Points of Interest, United States"
                         LayerItemsMode="Tree"
                         ShowOnlyVisibleLayers="False"
                         Refreshed="Legend_Refreshed">
                <esri:Legend.MapLayerTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <CheckBox Content="{Binding Label}"
                  IsChecked="{Binding IsEnabled, Mode=TwoWay}"
                  IsEnabled="{Binding IsInScaleRange}" >
                            </CheckBox>
                            <Slider Maximum="1" Value="{Binding Layer.Opacity, Mode=TwoWay}" Width="50" />
                        </StackPanel>
</DataTemplate>
                </esri:Legend.MapLayerTemplate>
                <esri:Legend.LayerTemplate>
                    <DataTemplate>
                        <CheckBox Content="{Binding Label}"
                   IsChecked="{Binding IsEnabled, Mode=TwoWay}"
                IsEnabled="{Binding IsInScaleRange}" >
                        </CheckBox>
                    </DataTemplate>
                </esri:Legend.LayerTemplate>
            </esri:Legend>
        </Border>


LegendDialog User Control code behind:
private void Legend_Refreshed(object sender, Legend.RefreshedEventArgs e)
        {
            if (e.LayerItem.LayerItems != null)
            {
                foreach (LayerItemViewModel layerItemVM in e.LayerItem.LayerItems)
                    if (layerItemVM.IsExpanded)
                        layerItemVM.IsExpanded = false;
            }
            else
            {
                e.LayerItem.IsExpanded = false;
            }
        }


My Mainpage.xaml contains to reference Map object and the Legend widget:


  <esri:Map x:Name="MyMap" Extent="-15000000,2000000,-7000000,8000000">
            <esri:ArcGISTiledMapServiceLayer ID="Street Map"
                    Url="http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer"/>
            <esri:ArcGISDynamicMapServiceLayer ID="United States" Opacity="0.6"
                    Url="http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Specialty/ESRI_StateCityHighway_USA/MapSe..."/>
            <esri:FeatureLayer ID="Points of Interest"
                    Url="http://sampleserver3.arcgisonline.com/ArcGIS/rest/services/Fire/Sheep/MapServer/0" />
        </esri:Map>

<local:LegendDialog x:Name="MyLegendDialog" Canvas.Left="430" Canvas.Top="200" Height="225" Width="218" Visibility="Collapsed" />

Thanks,
Sergio
0 Kudos
DominiqueBroux
Esri Frequent Contributor

<esri:Legend Map="{Binding ElementName=MyMap}"
This binding can't work if 'MyMap' is not in the same xaml file.

One way to make working is to create a Map dependency property in your LegendDialog control by using the code Darina gave.
Then in LegendDialog.xaml you can set a template binding to the map:
<esri:Legend Map="{TemplateBinding Map}" .....


and in your main page, you can set the Map property of your LegendDialog by using an element name:
 
<local:LegendDialog x:Name="MyLegendDialog" Canvas.Left="430" Canvas.Top="200" Height="225" Width="218" Visibility="Collapsed"
     Map="{Binding ElementName=MyMap}" 
 />
0 Kudos
SergioC
Emerging Contributor
I tired the above solution but when using :
<local:LegendDialog x:Name="MyLegendDialog" Canvas.Left="430" Canvas.Top="200" Height="225" Width="218" Visibility="Collapsed"
     Map="{Binding ElementName=MyMap}"
/>
I get the following error..........The property 'Map' was not found in type LegendDialog


I also added this to my code behind of the user control, but was not sure what was needed in the private void PopulateLayerList()



        protected static readonly DependencyProperty MapProperty =
             DependencyProperty.Register("Map", typeof(Map), typeof(LegendDialog), new PropertyMetadata(OnMapPropertyChanged));

        public Map Map
        {

            get { return (Map)this.GetValue(MapProperty); }
            set
            {
                this.SetValue(MapProperty, (DependencyObject)value);
                MessageBox.Show("in the GetValue(MapProperty");
            }
        }
        private static void OnMapPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            (d as LegendDialog).PopulateLayerList();
            MessageBox.Show("in the OnMapPropertyChanged");
        }

         private void PopulateLayerList()
        {          
            if (this.Map != null)
            {                                     
                //..what goes here........
            }
        }
0 Kudos
DominiqueBroux
Esri Frequent Contributor
I get the following error..........The property 'Map' was not found in type LegendDialog


Looks like the Map property is not existing in your LegendDialog. Had you added your code when you got this error:
protected static readonly DependencyProperty MapProperty =
DependencyProperty.Register("Map", typeof(Map), typeof(LegendDialog), new PropertyMetadata(OnMapPropertyChanged));
 
public Map Map
{
 
get { return (Map)this.GetValue(MapProperty); }
set
{
this.SetValue(MapProperty, (DependencyObject)value);
MessageBox.Show("in the GetValue(MapProperty");
}
}
private static void OnMapPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MessageBox.Show("in the OnMapPropertyChanged");
}
 


not sure what was needed in the private void PopulateLayerList()

At first glance you don't need it. My suggestion is to get rid of it.
0 Kudos
SergioC
Emerging Contributor
Yep, I added the code in the code behind, but I still get an error in the legenddialog.xaml indicating the ..........The property 'Map' was not found in type 'Control'


Still not sure if this is correct....

public Map Map
        {
            get { return (Map)GetValue(MapProperty); }
            set { SetValue(MapProperty, value); }
        }

        public static readonly DependencyProperty MapProperty =
             DependencyProperty.Register("Map", typeof(Map), typeof(LegendDialog), new PropertyMetadata(null, OnMapPropertyChanged));


        //private void PopulateLayerList()
        private static void OnMapPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  {
   //////what should go here, if anything....
  }
0 Kudos