Элемент WPF для использования для растрового изображения BackgroundImage с возможностью добавления дочерних элементов управления - PullRequest
0 голосов
/ 08 января 2019

Поэтому я использую приложение WinForms в качестве руководства для создания приложения WPF, отображающего морские карты. Приложение WinForms использует System.Windows.Forms.Panel и устанавливает для BackgroundImage динамически создаваемое растровое изображение. У меня есть необходимость добавить дочерний холст (или альтернативный элемент управления) на панель, чтобы я мог отображать путевые точки с наложением на график.

В WPF я пытался использовать Canvas вместо Panel, но мне не удалось заставить его работать, поскольку у него есть свойство Background, принимающее Brush, а не Bitmap.

Я также пытался использовать WinFormsHost для использования объекта System.Windows.Forms.Panel, но я не могу использовать это для размещения в нем другого элемента управления.

Так что мне нужен предпочтительно элемент WPF, который я могу использовать без WinFormsHost, который я могу использовать для установки растрового фона BackgroundImage и добавления другого элемента управления с наложенной графикой.

Ответы [ 3 ]

0 голосов
/ 09 января 2019

Всякий раз, когда вам нужно отобразить список чего-либо в WPF, вы обычно используете ItemsControl, и этот случай ничем не отличается.

С ItemsControl вы можете переопределить панель, которую он использует, и вы правильно выбрали Canvas вместо Panel. Установить изображение в качестве фона очень просто, вместо этого вы просто используете ImageBrush.

Что касается ваших путевых точек, я предполагаю, что вам нужно будет отображать и другие типы объектов, поэтому создайте ViewModel для каждого и используйте DataTemplate, чтобы выбрать соответствующий рисунок в зависимости от типа. Каждый созданный рисунок будет помещен в ContentPresenter, но ItemsControl также позволяет вам переопределить его стиль с помощью ItemContainerStyle, так что именно здесь вы устанавливаете Canvas.Left и Canvas.Top для позиционирования ваших элементов.

Соберите все вместе, и ваш XAML должен выглядеть примерно так:

<Viewbox>
    <ItemsControl ItemsSource="{Binding ChartElements}" Width="1000" Height="1000">
        <ItemsControl.Resources>
            <!-- DataTemplates here select the appropriate graphic to display for each class type -->
            <DataTemplate DataType="{x:Type local:Waypoint}">
                <Ellipse Width="50" Height="50" Fill="Yellow" Stroke="CornflowerBlue" StrokeThickness="5">
                    <Ellipse.RenderTransform>
                        <TranslateTransform X="-25" Y="-25" /> <!-- center the ellipse -->
                    </Ellipse.RenderTransform>
                </Ellipse>
            </DataTemplate>
            <DataTemplate DataType="{x:Type local:NavigationLine}">
                <Line X1="0" Y1="0" X2="{Binding Width}" Y2="{Binding Height}" Stroke="CornflowerBlue" StrokeThickness="10" StrokeDashArray="3 1" />
            </DataTemplate>
        </ItemsControl.Resources>
        <ItemsControl.ItemsPanel>
            <!-- Replace the default panel with a Canvas -->
            <ItemsPanelTemplate>
                <Canvas>
                    <Canvas.Background>
                        <ImageBrush ImageSource="https://images-na.ssl-images-amazon.com/images/I/A1%2Bp%2BB8wq2L._SL1500_.jpg" Stretch="Uniform" />
                    </Canvas.Background>
                </Canvas>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <!-- Position each item on the canvas and set the ZIndex so that waypoints appear on top -->
        <ItemsControl.ItemContainerStyle>
            <Style TargetType="{x:Type ContentPresenter}">
                <Setter Property="Canvas.Left" Value="{Binding X}" />
                <Setter Property="Canvas.Top" Value="{Binding Y}" />
                <Setter Property="Panel.ZIndex" Value="{Binding Layer}" />
            </Style>
        </ItemsControl.ItemContainerStyle>
    </ItemsControl>
</Viewbox>

Вы всегда должны создавать MainViewModel и делать правильный MVVM, но этот пример кода поможет вам начать:

public partial class MainWindow : Window
{
    public List<object> ChartElements { get; } = new List<object>
    {
        new Waypoint{X=100, Y=100 },
        new Waypoint{X=500, Y=300 },
        new Waypoint{X=300, Y=500 },
        new Waypoint{X=800, Y=700 },

        new NavigationLine{X1=100, Y1=100, X2=500, Y2=300},
        new NavigationLine{X1=500, Y1=300, X2=300, Y2=500},
        new NavigationLine{X1=300, Y1=500, X2=800, Y2=700}
    };

    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = this;
    }
}

public class Waypoint
{
    public int Layer { get; } = 1; // waypoint circles should always appear on top
    public double X { get; set; }
    public double Y { get; set; }
}

public class NavigationLine
{
    public int Layer { get; } = 0;
    public double X1 { get; set; }
    public double Y1 { get; set; }
    public double X2 { get; set; }
    public double Y2 { get; set; }

    public double X => this.X1;
    public double Y => this.Y1;
    public double Width => this.X2 - this.X1;
    public double Height => this.Y2 - this.Y1;
}

Загрузка изображения с горячей ссылкой, вероятно, займет несколько секунд, но вы должны получить следующее:

enter image description here

0 голосов
/ 09 января 2019

Вы, вероятно, могли бы сэкономить много работы, используя одну из библиотек управления картами, доступных для WPF. Я могу порекомендовать мой XAML Map Control . Он предоставляет несколько способов отображения растровых изображений на карте, а также имеет MapItemsControl для простого отображения коллекций элементов с географическим расположением.

Вероятно, самый простой способ показать навигационную карту - это использовать WmsImageLayer:

xmlns:map="clr-namespace:MapControl;assembly=MapControl.WPF"
...
<map:Map Center="50,0" ZoomLevel="2">
    <map:Map.MapLayer>
        <map:WmsImageLayer ServiceUri="http://chartserver4.sevencs.com:8080"
                           Layers="ENC"/>
    </map:Map.MapLayer>
</map:Map>

Если у вас нет WMS для создания диаграмм (например, SevenCs ChartServer), но необходимо преобразовать растровое изображение WinForms, вы можете получить его из MapImageLayer класса:

public class ChartImageLayer : MapImageLayer
{
    protected override Task<ImageSource> GetImageAsync(BoundingBox boundingBox)
    {
        // use ParentMap.MapProjection to get the current map projection

        return Task.Run(() =>
        {
            // get SevenCs chart bitmap for the requested bounding box
            System.Drawing.Bitmap chartBitmap = ...

            // convert from System.Drawing.Bitmap to System.Windows.Media.ImageSource
            using (var stream = new MemoryStream())
            {
                chartBitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
                stream.Position = 0;

                var bitmapImage = new BitmapImage();
                bitmapImage.BeginInit();
                bitmapImage.StreamSource = stream;
                bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
                bitmapImage.EndInit();
                bitmapImage.Freeze();
                return (ImageSource)bitmapImage;
            }
        });
    }
}

и используйте класс следующим образом:

<map:Map Center="50,0" ZoomLevel="2">
    <map:Map.MapLayer>
        <local:ChartImageLayer />
    </map:Map.MapLayer>
</map:Map>

Для отображения коллекций элементов вы должны создать модель вида, например, как это:

using MapControl;
...

public class Waypoint
{
    public string Label { get; set; }
    public Location Location { get; set; }
}

public class ViewModel
{
    public ObservableCollection<Waypoint> Waypoints { get; }
        = new ObservableCollection<Waypoint>();
}

с некоторой инициализацией, такой как:

public MainWindow()
{
    InitializeComponent();

    var vm = new ViewModel();
    vm.Waypoints.Add(new Waypoint { Label = "Iceland", Location = new Location(65, -18) });
    vm.Waypoints.Add(new Waypoint { Label = "Norway", Location = new Location(71, 25) });
    vm.Waypoints.Add(new Waypoint { Label = "Cyprus", Location = new Location(35, 33) });
    vm.Waypoints.Add(new Waypoint { Label = "Tenerife", Location = new Location(28.25, -16.5) });

    DataContext = vm;
}

В XAML вы бы добавили MapItemsControl следующим образом:

<map:Map Center="50,0" ZoomLevel="2">
    ...
    <map:MapItemsControl ItemsSource="{Binding Waypoints}">
        <map:MapItemsControl.ItemContainerStyle>
            <Style TargetType="map:MapItem">
                <Setter Property="map:MapPanel.Location" Value="{Binding Location}"/>
            </Style>
        </map:MapItemsControl.ItemContainerStyle>
        <map:MapItemsControl.ItemTemplate>
            <DataTemplate>
                <Canvas>
                    <Path Fill="Red">
                        <Path.Data>
                            <EllipseGeometry RadiusX="5" RadiusY="5"/>
                        </Path.Data>
                    </Path>
                    <TextBlock Margin="5,-5" Text="{Binding Label}"/>
                </Canvas>
            </DataTemplate>
        </map:MapItemsControl.ItemTemplate>
    </map:MapItemsControl>
</map:Map>

Результат:

enter image description here

Увеличено:

enter image description here

0 голосов
/ 08 января 2019

Вы можете использовать ImageBrush для установки фона https://docs.microsoft.com/en-us/dotnet/api/system.windows.media.imagebrush?view=netframework-4.7.2

Однако есть большой недостаток в том, что ваш холст не будет соответствовать размеру изображения. Вы можете в основном использовать изображение за холстом:

<Grid>
    <Image Source="..."/>
    <Canvas ...>
    </Canvas>
</Grid>
...