当前位置 : 主页 > 手机开发 > harmonyos >

图层管理器的设计与实现

来源:互联网 收集:自由互联 发布时间:2023-08-25
图层管理器的设计与实现。 首先我们明确一下,需要实现的五个小功能: 图层的加载,图层的显示,图层的移动,图层的移除,缩放至图层 其中,前三个是老师给的代码,后两个自己

图层管理器的设计与实现。

首先我们明确一下,需要实现的五个小功能:
图层的加载,图层的显示,图层的移动,图层的移除,缩放至图层
其中,前三个是老师给的代码,后两个自己实现。

1、界面设计

添加ListBox控件,并将名称设置为LayerListBox,双击添加ListBox的PreviewMouseMove和Drop两个事件。

<ListBox x:Name="LayerListBox" PreviewMouseDown="LayerListBox_PreviewMouseMove" Drop="LayerListBox_Drop" HorizontalAlignment="Left" Height="450" VerticalAlignment="Top" Width="150" Margin="0,61,0,-0.333"/>

2、c#代码实现

代码注释还未完善,请将就一下(#.#)

// 图层管理器
//将图层名增加到ListBox列表框中,该函数AddLayerNameToList可在打开Geodatabase,加载shp等函数中调用
private void AddLayerNameToList()
{
    LayerListBox.Items.Clear(); // 每次调用一次这个函数,就会先清楚之前的图层
    LayerListBox.AllowDrop = true;  //获取或设置一个值,该值指示此元素能否用作拖放操作的目标。这是依赖项属性。
    LayerCollection pLayers = MyMapView.Map.OperationalLayers;  //图层的集合
    if (pLayers.Count <= 0)  //如果图层为空,则返回退出
        return;
    for (int i = pLayers.Count - 1; i >= 0; i--)
    {
        // 加入CheckBox===========
        CheckBox cb = new CheckBox()  // 表示一个控件,用户可以选择和清除
        {
            Margin = new Thickness()  // 获取或设置元素的外边距
            {
                Left = 0,
                Top = 5,
                Right = 0,
                Bottom = 5
            }
        };
        // 图层名字
        cb.Content = pLayers[i].Name;  // 获取或设置元素内容
        cb.ToolTip = cb.Content;  // 获取或设置为在此元素显示的工具提示对象
        //图层名字, 即图层索引,名字可能不能以数字开头
        cb.Name = pLayers[i].Name;  // 获取或设置元素的标识名称
        // 默认选中状态
        cb.IsChecked = true;
        // 禁用聚焦
        cb.Focusable = false;
        // 下面的两行代码,就类似于监视了,比如这个图层给动了,就会触发RoutedEventHandler这个方法,然后再调用对应方法
        cb.Checked += new RoutedEventHandler(Checked_Layers_CheckBox);
        cb.Unchecked += new RoutedEventHandler(UnChecked_Layers_CheckBox);
        LayerListBox.Items.Add(cb);
        // 加入CheckBox===========
        // 下面这只有一行,就只是显示图层,不能进行拖拽和是否显示操作
        // LayerListBox.Items.Add(pLayers[i].Name);
    }
}
//设置所选择的图层可见
private void Checked_Layers_CheckBox(object sender, RoutedEventArgs e)
{
    CheckBox cb = sender as CheckBox;
    cb.IsChecked = true;
    int index = GetLayerIndex(cb.Name);
    MyMapView.Map.OperationalLayers[index].IsVisible = true;
}
//设置所选择的图层不可见
private void UnChecked_Layers_CheckBox(object sender, RoutedEventArgs e)
{
    CheckBox cb = sender as CheckBox;
    cb.IsChecked = false;
    int index = GetLayerIndex(cb.Name);
    MyMapView.Map.OperationalLayers[index].IsVisible = false;
}
//?????????
internal static class Utils
{
    //根据子元素查找父元素
    public static T FindVisualParent<T>(DependencyObject obj) where T : class
    {
        while (obj != null)
        {
            if (obj is T)
                return obj as T;
            obj = System.Windows.Media.VisualTreeHelper.GetParent(obj);
        }
        return null;
    }
}

//由图层名获取图层
private Layer GetLayer(string name)
{
    LayerCollection pLayers = MyMapView.Map.OperationalLayers;
    for (int i = pLayers.Count - 1; i >= 0; i--)
    {
        if (pLayers[i].Name.Equals(name))
        {
            return pLayers[i];
        }
    }
    return null;
}

//由图层名获取图层index
private int GetLayerIndex(string name)
{
    LayerCollection pLayers = MyMapView.Map.OperationalLayers;
    for (int i = pLayers.Count - 1; i >= 0; i--)
    {
        if (pLayers[i].Name.Equals(name))
        {
            return i;
        }
    }
    return -1;
}

// MoveLayer 函数,地图中图层顺序移动
private void MoveLayer(string sourceLayer, string targetLayer)
{
    LayerCollection pLayers = MyMapView.Map.OperationalLayers;
    Layer sourcLayer = GetLayer(sourceLayer);
    pLayers.Remove(sourcLayer);  // 移除移动图层
    int targerIndex = GetLayerIndex(targetLayer);  // 获取目标图层的索引
    Console.WriteLine(targerIndex);  // 移动图层到最顶部时,输出为0
    pLayers.Insert(targerIndex + 1, sourcLayer);  // 插入在目标图层的上面
    // 为什么下面的不需要+1,这里需要?????懵o(╥﹏╥)o
    // 10-20 问老师了,老师这么解释,LayerListBox里的子元素,索引越大的图层是越靠前的,所以这里得+1
    //LayerListBox.Items.Insert(LayerListBox.Items.IndexOf(targetPerson), sourcePerson);
}

// 移动图层(总
private void LayerListBox_Drop(object sender, DragEventArgs e)  // 感觉这种事件函数,就是起实时监视作用,比如LayerListBox的界面被拖拽了,就会触发这个函数
{
    var pos = e.GetPosition(LayerListBox);  // 返回相对于指定的拖动点,就是鼠标拖动图层放下后的那个位置
    Console.WriteLine("pos:" + pos);  // pos:98.6666666666667,11.6666666666667
    var result = System.Windows.Media.VisualTreeHelper.HitTest(LayerListBox, pos);  // ?
    Console.WriteLine("result:" + result);  // result:System.Windows.Media.PointHitTestResult
    if (result == null)
    {
        return;
    }
    //查找源数据
    var sourcePerson = e.Data.GetData(typeof(CheckBox)) as CheckBox;  // 查找移动的图层
    Console.WriteLine("sourcePerson:" + sourcePerson);  // sourcePerson:System.Windows.Controls.CheckBox 内容:cn_province_400_1988_WGS84 IsChecked:True
    if (sourcePerson == null)
    {
        return;
    }
    //查找目标数据
    var listBoxItem = Utils.FindVisualParent<ListBoxItem>(result.VisualHit);  // 查找移动图层后的位置的原来图层
    Console.WriteLine("listBoxItem:" + listBoxItem);  // listBoxItem:System.Windows.Controls.ListBoxItem: cn_river_400_1988_WGS84
    if (listBoxItem == null)  // 这里有点疑惑,那把图层移动到最顶部,不就是null了  解:只有一个图层的时候才会是null
    {
        return;
    }
    var targetPerson = listBoxItem.Content as CheckBox;
    if (ReferenceEquals(targetPerson, sourcePerson))  // 判断两个图层是否一样,一样的话就是拖拽了,但位置没变
    {
        return;
    }
    LayerListBox.Items.Remove(sourcePerson);
    Console.WriteLine("LayerListBox.Items.IndexOf(targetPerson):" + LayerListBox.Items.IndexOf(targetPerson));  // LayerListBox.Items.IndexOf(targetPerson):0
    LayerListBox.Items.Insert(LayerListBox.Items.IndexOf(targetPerson), sourcePerson);
    //地图中图层顺序移动
    MoveLayer(sourcePerson.Name, targetPerson.Name);
}

// 监视鼠标事件
private void LayerListBox_PreviewMouseMove(object sender, MouseEventArgs e)
{
    Console.WriteLine("鼠标???");  // 左右键行为,都会触发此方法执行
    if (e.LeftButton == System.Windows.Input.MouseButtonState.Pressed)
    {
        var pos = e.GetPosition(LayerListBox);
        Console.WriteLine("pos:"+ pos);  // 获取鼠标点击的位置,上面的pos的坐标是获取鼠标放下的位置
        if (pos.X < 20)
            //默认为选中的checkbox 的方框,不进行图层移动,退出该函数 wxf 2019-5-22。
            return;
        System.Windows.Media.HitTestResult result = System.Windows.Media.VisualTreeHelper.HitTest(LayerListBox, pos);
        Console.WriteLine("LayerListBox_PreviewMouseMove result:" + result);  // 输出:LayerListBox_PreviewMouseMove result:System.Windows.Media.PointHitTestResult
        if (result == null)
            return;
        var listBoxItem = Utils.FindVisualParent<ListBoxItem>(result.VisualHit);
        Console.WriteLine("listBoxItem:"+ listBoxItem);  // 输出 listBoxItem:System.Windows.Controls.ListBoxItem: cn_river_400_1988_WGS84
        if (listBoxItem == null)//|| listBoxItem.Content != LayerListBox.SelectedItem)
        {
            return;
        }
        DataObject dataObj = new DataObject(listBoxItem.Content as CheckBox);
        Console.WriteLine("dataObj:"+ dataObj);  // 输出 dataObj:System.Windows.DataObject
        DragDrop.DoDragDrop(LayerListBox, dataObj, DragDropEffects.Move);
        // 这里执行完毕,才开始执行 LayerListBox_Drop函数
//猜想,会不会是因为这最后一行,改变了某个值,从而导致监视这个值的方法被触发???
    }
}

试着从DragEventArgs元数据理解:

图层管理器的设计与实现_Windows

猜想,会不会是因为这最后一行,改变了某个值,从而导致监视这个值的方法被触发???
DragDrop.DoDragDrop(LayerListBox, dataObj, DragDropEffects.Move);
没有注释时,移动图层时的控制台输出:

图层管理器的设计与实现_Windows_02

把上面这一行给注释掉,运行代码,当我拖拽的时候,通过控制台输出可以发现,果真没有执行 LayerListBox_Drop函数,而且图层也不会发生移动,感觉猜想是对了。

所以,应该就是DragDrop.DoDragDrop(LayerListBox, dataObj, DragDropEffects.Move);,这一行代码,触发了LayerListBox_Drop函数执行!

3、结果

注:Console.WriteLine(); 这个方法就类似于Python的print()、java的System.out.println(),在目前刚接触C#之前,还不知道其它debugger方式,个人觉得Console.WriteLine(); 就是一个很好的方式!

4、图层移除功能和缩放至图层功能实现

注意下面所说的Menu控件是指下图这个:

图层管理器的设计与实现_图层_03

a.界面设计

设计鼠标右击时的弹框:

图层管理器的设计与实现_图层_04


增加属性 Visibility="Hidden"用于控制其显示或隐藏。

图层管理器的设计与实现_Windows_05

<Menu x:Name="rightClick" Height="64" Width="100" BorderBrush="Aquamarine" BorderThickness="2" Panel.ZIndex="999" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,0,0,0" Visibility="Hidden">
    <MenuItem Header="删除" Height="30" Width="96" BorderBrush="Pink" BorderThickness="2" FontSize="15" Click="DeleteLayer_Click"/>
    <MenuItem Header="缩放至图层" Height="30" Width="96" BorderBrush="Pink" BorderThickness="2" FontSize="15" Click="Scale_Zoom"/>
</Menu>

这里的话,就先创建好鼠标的点击事件程序了。

b.代码设计

首先,基于老师原有代码。
我们可以知道,在监视鼠标的那部分代码中,老师只用了if语句判断是否为鼠标左键操作,那我们可以加个else if语句,用于判断是否为鼠标右键操作,于是:

else if (e.RightButton == System.Windows.Input.MouseButtonState.Pressed)
{
    var pos = e.GetPosition(LayerListBox);// 获取鼠标点击的位置
    rightClick.Margin = new Thickness(pos.X, pos.Y + 60, 0, 0);
    System.Windows.Media.HitTestResult result = System.Windows.Media.VisualTreeHelper.HitTest(LayerListBox, pos);
    if (result == null)
        return;
    var listBoxItem = Utils.FindVisualParent<ListBoxItem>(result.VisualHit);
    if (listBoxItem == null)//|| listBoxItem.Content != LayerListBox.SelectedItem)
    {
        rightClick.Visibility = Visibility.Hidden;
        return;
    }
    delete_name = listBoxItem.Content as CheckBox;
    rightClick.Visibility = Visibility.Visible;
}

下面来解释一下,各行代码意思:
var pos = e.GetPosition(LayerListBox); 用于获取鼠标相对于窗口界面点击时的坐标位置。

rightClick.Margin = new Thickness(pos.X, pos.Y + 60, 0, 0);rightClick是界面设计时,给Menu控件加的x:Name属性,不记得可以认真看看上面的代码哦

rightClick.Margin 就是获取Menu控件的外边距,new Thickness(pos.X, pos.Y + 60, 0, 0)是设置该控件的相对于窗口的位置;

大家想一下,要想设置控件的位置,我们是不是只需要操作Margin.Left和Margin.Top的值就行了。

图层管理器的设计与实现_Windows_06


至于怎么让显示的位置跟我们鼠标右击的位置一样呢,这里的pos.X,pos.Y就派上用场了

然后为什么pos.Y要加60呢,因为,获取的鼠标坐标是相对于窗口坐标的,ListBox控件的顶部是不是有另外的控件,那我们要排除它们的影响,就需要加上它们的高度。

图层管理器的设计与实现_Windows_07

那这样,为什么不把Menu这个控件放在ListBox里呢,因为由于加载图层的影响,它会把这个Menu控件给清除,所以,个人觉得如果要放在里面,可能需要用c#代码来同步,就是每次加载图层时,同时添加新的Menu控件。感兴趣的小伙伴可以去尝试尝试!

结果就如下图:

图层管理器的设计与实现_System_08

上图结果还需要加载下面代码:

System.Windows.Media.HitTestResult result = System.Windows.Media.VisualTreeHelper.HitTest(LayerListBox, pos);// 命中测试的结果,根据其类型判断其是否符合我们的测试要求,如果不符合则返回HitTestResultBehavior.Continue。这里的`LayerListBox`是ListBox的x:Name属性。
if (result == null) // 判断是否为空
    return;
var listBoxItem = Utils.FindVisualParent<ListBoxItem>(result.VisualHit); // 获取右击的图层
if (listBoxItem == null)//|| listBoxItem.Content != LayerListBox.SelectedItem)// 判断右击位置是否为LayerListBox的子项
{
    rightClick.Visibility = Visibility.Hidden;  // 这里为什么要隐藏呢,隐藏存在第一次右击是图层,接着第二次右击不是图层,那就是的再隐藏了。
    return;
}
delete_name = listBoxItem.Content as CheckBox;  // delete_name 是一个自定义全局变量,用于传右击的图层对象
rightClick.Visibility = Visibility.Visible;  // 设置Menu控件可见

图层管理器的设计与实现_System_09

到这里,我们就完成了,控件跟随鼠标右击位置时的显示与隐藏了。接下来,是要实现鼠标右击后,点击移除图层缩放至图层的功能实现
解析请看代码注释

// 移除图层
private void DeleteLayer_Click(object sender, RoutedEventArgs e)
{
    rightClick.Visibility = Visibility.Hidden;  // 触发点击事件后,隐藏Menu控件
    LayerCollection pLayers = MyMapView.Map.OperationalLayers;  // 获取真实图层
    Layer sourcLayer = GetLayer(delete_name.Name);  // 通过delete_name.Name获取图层对象,就是鼠标右击时的那个图层
    pLayers.Remove(sourcLayer);
// 移除鼠标右击时的图层,这里移除了,只是移除了界面显示的图层,而ListBox里的那个图层还没移除,就是鼠标右击时的那个位置的图层。下面的这一行就是移除ListBox里的图层。
    LayerListBox.Items.Remove(delete_name);
// 如果还是不明白的话,试试注释这一行代码,然后启动代码,执行图层删除操作,你就会明白了!
}

// 缩放至图层
private async void Scale_Zoom(object sender, RoutedEventArgs e)
{
    rightClick.Visibility = Visibility.Hidden;  // 触发点击事件后,隐藏Menu控件
    LayerCollection pLayers = MyMapView.Map.OperationalLayers;  // 获取真实图层
    Layer sourcLayer = GetLayer(delete_name.Name);  // 通过delete_name.Name获取图层对象,就是鼠标右击时的那个图层
    //缩放到提供的几何体。即使几何体不规则,下面的函数会自动找出可以包含进所有外形的矩形,并向外扩展50像素。
    await MyMapView.SetViewpointGeometryAsync(sourcLayer.FullExtent, 50);
// 因为缩放至显示,只是该图层放大至显示范围,不涉及ListBox操作
}

查看演示视频 功能实现完毕O(∩_∩)O~
如有错误,欢迎评论指正哈

箴言:因为这些东西是非常简单的。不要抱怨自己学不会,那是因为你没有足够用心。



上一篇:iOS 知识-常用小技巧大杂烩
下一篇:没有了
网友评论