图层管理器的设计与实现。
首先我们明确一下,需要实现的五个小功能:
图层的加载,图层的显示,图层的移动,图层的移除,缩放至图层
其中,前三个是老师给的代码,后两个自己实现。
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元数据理解:
猜想,会不会是因为这最后一行,改变了某个值,从而导致监视这个值的方法被触发???DragDrop.DoDragDrop(LayerListBox, dataObj, DragDropEffects.Move);没有注释时,移动图层时的控制台输出:
把上面这一行给注释掉,运行代码,当我拖拽的时候,通过控制台输出可以发现,果真没有执行 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控件
是指下图这个:
a.界面设计
设计鼠标右击时的弹框:
增加属性 Visibility="Hidden"
用于控制其显示或隐藏。
<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的值就行了。
至于怎么让显示的位置跟我们鼠标右击的位置一样呢,这里的pos.X,pos.Y就派上用场了
然后为什么pos.Y要加60呢,因为,获取的鼠标坐标是相对于窗口坐标的,ListBox控件的顶部是不是有另外的控件,那我们要排除它们的影响,就需要加上它们的高度。
那这样,为什么不把Menu这个控件放在ListBox里呢,因为由于加载图层的影响,它会把这个Menu控件给清除,所以,个人觉得如果要放在里面,可能需要用c#代码来同步,就是每次加载图层时,同时添加新的Menu控件。感兴趣的小伙伴可以去尝试尝试!
结果就如下图:
上图结果还需要加载下面代码:
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控件可见
到这里,我们就完成了,控件跟随鼠标右击位置时的显示与隐藏了。接下来,是要实现鼠标右击后,点击移除图层
和缩放至图层
的功能实现
解析请看代码注释
// 移除图层
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~
如有错误,欢迎评论指正哈
箴言:因为这些东西是非常简单的。不要抱怨自己学不会,那是因为你没有足够用心。