Select to view content in your preferred language

How To: Reverse layers order in listbox

2331
9
08-12-2010 01:28 PM
MikeKaufman
Emerging Contributor
Following the example found here
Esri Layer sample

How would I reverse the order of the layers so that the bottom layer is on the bottom of list moving up to the top layer being at the top of the list?
0 Kudos
9 Replies
JenniferNery
Esri Regular Contributor
You can do something like to change the current order of your layers in code-behind.

            LayerCollection layers = this.MyMap.Layers;            
            IEnumerable<Layer> newOrder = layers.Reverse();
            LayerCollection newLayers = new LayerCollection();
            foreach (Layer l in newOrder)
                newLayers.Add(l);
            layers.Clear();
            foreach (Layer l in newLayers)
                layers.Add(l);


Jennifer
0 Kudos
TerryGiles
Frequent Contributor
how can you reverse the order in the list and maintain the binding w/ the layer visibility and opacity?  Would I need to create another control w/ 2 LayerCollections and (two way)bind one to the ListBox and the other actual map layers with some code to handle changes?  Or am I over thinking this?  Thanks, Terry
0 Kudos
JenniferNery
Esri Regular Contributor
Binding with Layer properties can be done instead of
{Binding ElementName=MyMap, Path=Layers[0].Opacity}

a better way would be
{Binding ElementName=MyMap, Path=Layers[LayerID].Opacity}

so that any changes in the order will not affect your binding.
0 Kudos
MikeKaufman
Emerging Contributor
So I figured out how accomplish this issue basing it on some of the responses.
One issue I ran into with just returning a new reversed layer collection was it would not  update the listbox when a layer was added at run time.  I could not figure out how to make binding rebind when NotifyCollectionChanged happen on my layer collection.

What I ended up doing was adding a new property in my ViewModel that reverse and filtered my layer collection. 
    Public ReadOnly Property FilteredViewableLayersList As List(Of Layer)
        Get
            Dim layerlist =
                From lc In _layerCollection
                Where lc.ID <> mcsHighlightedLayerName _
                AndAlso lc.ID <> String.Empty

            Return layerlist.Reverse.ToList
        End Get
    End Property

I then handled the Collection changed event calling OnPropertyChanged (which raises NotifyPropertyChanged) for the filtered Property.
    Private Sub _layerCollection_CollectionChanged(ByVal sender As Object, ByVal e As System.Collections.Specialized.NotifyCollectionChangedEventArgs) Handles _layerCollection.CollectionChanged
               OnPropertyChanged("FilteredViewableLayersList")
    End Sub

  This refreshed the XMAL binding.

The best part of all my I didn't need to change my Opacity or Visble Binding in my XMAL.

Hope this helps others that read this post.
0 Kudos
TerryGiles
Frequent Contributor
I ended up building the listbox in the cod behind as the layers in our map are added at startup.  I used Jennifer's method of binding to the layer's Id instead of index and it works great. There's probably a better way to set the margins and width of the controls, but it'll work for now.

XAML
  <Grid x:Name="LayoutRoot" Style="{StaticResource LayoutRootGridStyle}">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="100" />
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="100" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="40" />
                <RowDefinition Height="*"  />
            </Grid.RowDefinitions>

        <esri:Map x:Name="map" Grid.ColumnSpan="4" Grid.Row="1"  IsLogoVisible="False" >
            <esri:Map.Extent>
                <esri:Envelope XMin="-130" YMin="10" XMax="-70" YMax="60" >
                    <esri:Envelope.SpatialReference>
                        <esri:SpatialReference WKID="4326"/>
                    </esri:Envelope.SpatialReference>
                </esri:Envelope>
            </esri:Map.Extent>
            <esri:Map.Layers>
                <!--layers added at runtime -->
            </esri:Map.Layers>
        </esri:Map>

        <!--TOC-->
        <Border Background="#996495ED" BorderThickness="1" CornerRadius="5"
            HorizontalAlignment="Right"  VerticalAlignment="Top"
            Margin="20" Padding="5" BorderBrush="Black" Grid.Row="1" Grid.Column="0" >
            <ListBox x:Name="MyList" ></ListBox>
        </Border>
  </Grid>


CS
        void MapServicesLoadComplete(LoadOperation<Web.ApplicationMapServices> lo)
        {
            //Executes when the asynch domain service query completes; adds MapServices to the map

            Layer layer;
            ArcGISDynamicMapServiceLayer DynLayer = new ArcGISDynamicMapServiceLayer();
            ArcGISTiledMapServiceLayer TileLayer = new ArcGISTiledMapServiceLayer();


            foreach (var mapservice in lo.Entities)
         {
                if (mapservice.Cached == 1)
                {
                    TileLayer = new ArcGISTiledMapServiceLayer();
                    TileLayer.Url = mapservice.ServerURL + "/" + mapservice.MapServiceName + "/MapServer";
                    TileLayer.ID = mapservice.MapServiceName;

                    layer = TileLayer;
                } 
                else
                {
                    DynLayer = new ArcGISDynamicMapServiceLayer();
                    DynLayer.Url = "http://" + mapservice.AGSServerName + "/arcgis/rest/services/" + mapservice.MapServiceName + "/MapServer";
                    DynLayer.ID = mapservice.MapServiceName;
                    layer = DynLayer;
                }

                layer.Opacity =  mapservice.Transparancy == 0 ? 100 :   1- (Convert.ToDouble(mapservice.Transparancy)/100);
                layer.Visible = Convert.ToBoolean(mapservice.Active);


                map.Layers.Add(layer);

                MyList.Items.Insert(0, BuildTOCitem(layer));
           
         }

        }


        private StackPanel BuildTOCitem(Layer lyr)
        {
            StackPanel spNewItem = new StackPanel();
            CheckBox cbx = new CheckBox();
            Slider sldr = new Slider();
            TextBlock tblock = new TextBlock();

            spNewItem.Orientation = Orientation.Horizontal;

            Binding bind = new Binding("Layers[" + lyr.ID + "].Visible");
            bind.Mode = BindingMode.TwoWay;
            bind.ElementName = "map";
            cbx.SetBinding(CheckBox.IsCheckedProperty, bind);

            bind = new Binding("Layers[" + lyr.ID + "].Opacity"); 
            bind.Mode = BindingMode.TwoWay;
            bind.ElementName = "map";
            sldr.Margin = new Thickness(-5, 0, 0, 0);
            sldr.Width = 50;
            sldr.Minimum = 0;
            sldr.Maximum = 1;
            sldr.SetBinding(Slider.ValueProperty, bind);

            bind = new Binding("Layers[" + lyr.ID + "].ID");
            bind.Mode = BindingMode.OneWay;
            bind.ElementName = "map";
            tblock.Margin = new Thickness(5, 0, 0, 0);
            tblock.SetBinding(TextBlock.TextProperty,bind);

            spNewItem.Children.Add(cbx);
            spNewItem.Children.Add(sldr);
            spNewItem.Children.Add(tblock);

            return spNewItem;

        }
0 Kudos
KeithGanzenmuller
Frequent Contributor
Let me ask if there is something wrong with the way I did it, I was looking to both exclude some layers from the listbox and reverse the order.
What I did was remove the:
ItemsSource="{Binding ElementName=myMap, Path=Layers }" from the XAML leaving:

<ListBox x:Name="MyList" >

Then in my Code Behind I added the layers that I wanted in the order I wanted:
    Public Sub New()
        InitializeComponent()
        MyList.Items.Add(myMap.Layers.Item(2))
        MyList.Items.Add(myMap.Layers.Item(1))
        MyList.Items.Add(myMap.Layers.Item(0))
    End Sub

My question is actually  not whether it will work because it works for my application where I know the layers ahead of time but if there is a reason not to do it this way so I can change it in the future.

Keith
0 Kudos
JenniferNery
Esri Regular Contributor
I don't see anything wrong with your solution, if it works for you.  While most people prepare to use Binding to a collection for their ItemsSource, you can also add items to your ListBox in code-behind.
0 Kudos
KeithGanzenmuller
Frequent Contributor
Thanks Jennifer.

My main issue was that I couldn't figure out how to exclude layers that I didn't want to inlude in my listbox.

<ListBox x:Name="MyList" ItemsSource="{Binding ElementName=myMap, Path=Layers}" >

I tried Path=Layers[0] and other things like Path=Layers[0,1,2] just couldn't get it to work.

KG
0 Kudos
JenniferNery
Esri Regular Contributor
You can create a different instance of LayerCollection, copy the current LayerCollection on your Map and bind to that for ItemSource instead.  In this solution, you will still need to do stuff or extra handling on this LayerCollection in code-behind.

You're right Element Binding with Map as element and Layers as Path for this case will not work because you need to remove the layer from the map if you needed it excluded in your listbox.
0 Kudos