Тренировать новичка через базовый WPF?(Я не впадаю в это.) - PullRequest
12 голосов
/ 22 января 2011

Вопрос как звучит. Независимо от того, как сильно и как часто я пытаюсь понять WPF, мне кажется, что я бью себя об стену. Мне нравится Winforms, где все имеет смысл.

В качестве примера я пытаюсь написать простое приложение, которое позволит мне разметить группу двухмерных путей (представленных полилиниями) и перетащить их вершины, а также синхронизировать информацию о вершинах с докладчиком. (то есть, я полагаю, ViewModel)

Итак, проблема в следующем:

  • Заставить окно распознавать экземпляр IExtendedObjectPresenter в качестве источника данных;
  • Из коллекции IExtendedObject нарисуйте одну ломаную линию для каждого IExtendedObject;
  • Для каждой вершины в расширенном объекте, представленном коллекцией IExtendedObject.Points, поместите вершину ломаной линии в указанных координатах.

IDE мне здесь совсем не помогает. Ни одно из много свойств, доступных в XAML, не кажется мне значимым. Поскольку кажется, что так много делается неявно, нет очевидного места, чтобы просто сказать окну, что делать.

Прежде чем я получу намеченный срок и расскажу RTFM, я хотел бы еще раз подчеркнуть, что я много раз исследовал основные концепции WPF. Я знаю об этом не больше, чем когда он был впервые выпущен. Это кажется совершенно непроницаемым. Примеры, приведенные для одного типа поведения, никоим образом не применимы к даже немного другому типу поведения, поэтому вы вернулись к исходной точке. Я надеюсь, что повторение и целевые испытания могут в какой-то момент включить свет в моей голове.

Ответы [ 2 ]

26 голосов
/ 23 января 2011

Я сочувствую вам. Реальное понимание WPF занимает много времени, и может быть очень сложно выполнить простейшие вещи. Но погружение в проблему, которая непроста для экспертов, требует только неприятностей. Вам нужно решать более простые задачи и читать много кода, пока все не станет понятным. Дональд Кнут говорит, что вы не знаете материал, пока не выполните упражнения.

Я решил вашу проблему, и я признаю, что есть много передовых концепций, которые делают это аккуратно, и использование MVVM делает это намного сложнее. Что бы это ни стоило, вот решение с нулевым выделением кода для вашей проблемы в духе MVVM.

Вот XAML:

<Grid>
    <Grid.Resources>
        <local:PolylineCollection x:Key="sampleData">
            <local:Polyline>
                <local:Coordinate X="50" Y="50"/>
                <local:Coordinate X="100" Y="100"/>
                <local:Coordinate X="50" Y="150"/>
            </local:Polyline>
        </local:PolylineCollection>
    </Grid.Resources>
    <Grid DataContext="{StaticResource sampleData}">
        <ItemsControl ItemsSource="{Binding Segments}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <Canvas/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Line X1="{Binding Start.X}" Y1="{Binding Start.Y}" X2="{Binding End.X}" Y2="{Binding End.Y}" Stroke="Black" StrokeThickness="2"/>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
        <ItemsControl ItemsSource="{Binding ControlPoints}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <Canvas/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemContainerStyle>
                <Style TargetType="ContentPresenter">
                    <Setter Property="Canvas.Left" Value="{Binding X}"/>
                    <Setter Property="Canvas.Top" Value="{Binding Y}"/>
                </Style>
            </ItemsControl.ItemContainerStyle>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Ellipse Margin="-10,-10,0,0" Width="20" Height="20" Stroke="DarkBlue" Fill="Transparent">
                        <i:Interaction.Behaviors>
                            <local:ControlPointBehavior/>
                        </i:Interaction.Behaviors>
                    </Ellipse>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>
</Grid>

и вот вспомогательные классы:

public class Coordinate : INotifyPropertyChanged
{
    private double x;
    private double y;

    public double X
    {
        get { return x; }
        set { x = value; OnPropertyChanged("X", "Point"); }
    }
    public double Y
    {
        get { return y; }
        set { y = value; OnPropertyChanged("Y", "Point"); }
    }
    public Point Point
    {
        get { return new Point(x, y); }
        set { x = value.X; y = value.Y; OnPropertyChanged("X", "Y", "Point"); }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(params string[] propertyNames)
    {
        foreach (var propertyName in propertyNames)
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

public class Polyline : List<Coordinate>
{
}

public class Segment
{
    public Coordinate Start { get; set; }
    public Coordinate End { get; set; }
}

public class PolylineCollection : List<Polyline>
{
    public IEnumerable<Segment> Segments
    {
        get
        {
            foreach (var polyline in this)
            {
                var last = polyline.FirstOrDefault();
                foreach (var coordinate in polyline.Skip(1))
                {
                    yield return new Segment { Start = last, End = coordinate };
                    last = coordinate;
                }
            }
        }
    }

    public IEnumerable<Coordinate> ControlPoints
    {
        get
        {
            foreach (var polyline in this)
            {
                foreach (var coordinate in polyline)
                    yield return coordinate;
            }
        }
    }
}

public class ControlPointBehavior : Behavior<FrameworkElement>
{
    private bool mouseDown;
    private Vector delta;

    protected override void OnAttached()
    {
        var canvas = AssociatedObject.Parent as Canvas;
        AssociatedObject.MouseLeftButtonDown += (s, e) =>
        {
            mouseDown = true;
            var mousePosition = e.GetPosition(canvas);
            var elementPosition = (AssociatedObject.DataContext as Coordinate).Point;
            delta = elementPosition - mousePosition;
            AssociatedObject.CaptureMouse();
        };
        AssociatedObject.MouseMove += (s, e) =>
        {
            if (!mouseDown) return;
            var mousePosition = e.GetPosition(canvas);
            var elementPosition = mousePosition + delta;
            (AssociatedObject.DataContext as Coordinate).Point = elementPosition;
        };
        AssociatedObject.MouseLeftButtonUp += (s, e) =>
        {
            mouseDown = false;
            AssociatedObject.ReleaseMouseCapture();
        };
    }
}

В этом решении используются модели поведения, которые идеально подходят для реализации интерактивности с MVVM.

Если вы не знакомы с поведением, установите Expression Blend 4 SDK и добавьте следующие пространства имен:

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

и добавьте System.Windows.Interactivity к вашему проекту.

13 голосов
/ 23 января 2011

Я покажу, как создать приложение WPF с шаблоном MVVM для 2D-Poliline с перетаскиваемыми вершинами.

PointViewModel.cs

public class PointViewModel: ViewModelBase
{
    public PointViewModel(double x, double y)
    {
        this.Point = new Point(x, y);
    }

    private Point point;

    public Point Point
    {
        get { return point; }
        set
        {
            point = value;
            OnPropertyChanged("Point");
        }
    }
}

Класс ViewModelBase содержит только реализацию интерфейса INotifyPropertyChanged. Это необходимо для отражения изменений свойства clr на визуальном представлении.

LineViewModel.cs

public class LineViewModel
{
    public LineViewModel(PointViewModel start, PointViewModel end)
    {
        this.StartPoint = start;
        this.EndPoint = end;
    }

    public PointViewModel StartPoint { get; set; }
    public PointViewModel EndPoint { get; set; }
}

В нем есть ссылки на Баллы, поэтому изменения будут получены автоматически.

MainViewModel.cs

public class MainViewModel
{
    public MainViewModel()
    {
        this.Points = new List<PointViewModel>
        {
            new PointViewModel(30, 30),
            new PointViewModel(60, 100),
            new PointViewModel(50, 120)
        };
        this.Lines = this.Points.Zip(this.Points.Skip(1).Concat(this.Points.Take(1)),
            (p1, p2) => new LineViewModel(p1, p2)).ToList();
    }

    public List<PointViewModel> Points { get; set; }
    public List<LineViewModel> Lines { get; set; }
}

Содержит пример данных точек и линий

MainVindow.xaml

<Window.Resources>
    <ItemsPanelTemplate x:Key="CanvasPanelTemplate">
        <Canvas/>
    </ItemsPanelTemplate>
    <Style x:Key="PointListBoxItem">
        <Setter Property="Canvas.Left" Value="{Binding Point.X}"/>
        <Setter Property="Canvas.Top" Value="{Binding Point.Y}"/>
    </Style>
    <DataTemplate x:Key="LineTemplate">
        <Line X1="{Binding StartPoint.Point.X}" X2="{Binding EndPoint.Point.X}" Y1="{Binding StartPoint.Point.Y}" Y2="{Binding EndPoint.Point.Y}" Stroke="Blue"/>
    </DataTemplate>
    <DataTemplate x:Key="PointTemplate">
        <view:PointView />
    </DataTemplate>
</Window.Resources>
<Grid>
    <ItemsControl ItemsSource="{Binding Lines}" ItemsPanel="{StaticResource CanvasPanelTemplate}" ItemTemplate="{StaticResource LineTemplate}"/>
    <ItemsControl ItemsSource="{Binding Points}" ItemContainerStyle="{StaticResource PointListBoxItem}" ItemsPanel="{StaticResource CanvasPanelTemplate}"
                  ItemTemplate="{StaticResource PointTemplate}"/>
</Grid>

Здесь много хитростей. Прежде всего, эти ItemsControls основаны не на вертикальных StackPanel, а на Canvas. ItemsControl точек применяет специальный шаблон контейнера с целью размещения элементов в необходимых координатах. Но ItemsControl строк не требуют таких шаблонов, и это странно в какой-то момент. Два последних шаблона данных очевидны.

PointView.xaml

<Ellipse Width="12" Height="12" Stroke="Red" Margin="-6,-6,0,0" Fill="Transparent"/>

Левое и верхнее поля равны половине Width и Height. У нас есть прозрачный Fill, потому что это свойство не имеет значения по умолчанию и события мыши не работают.

Это почти все. Остается только функциональность drag-n-drop.

PointView.xaml.cs

public partial class PointView : UserControl
{
    public PointView()
    {
        InitializeComponent();

        this.MouseLeftButtonDown += DragSource_MouseLeftButtonDown;
        this.MouseMove += DragSource_MouseMove;
    }

    private bool isDraggingStarted;

    private void DragSource_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        this.isDraggingStarted = true;
    }

    private void DragSource_MouseMove(object sender, MouseEventArgs e)
    {
        if (isDraggingStarted == true)
        {
            var vm = this.DataContext as PointViewModel;
            var oldPoint = vm.Point;

            DataObject data = new DataObject("Point", this.DataContext);
            DragDropEffects effects = DragDrop.DoDragDrop(this, data, DragDropEffects.Move);

            if (effects == DragDropEffects.None) //Drag cancelled
                vm.Point = oldPoint;

            this.isDraggingStarted = false;
        }
    }

MainVindow.xaml.cs

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = new MainViewModel();

        this.AllowDrop = true;
        this.DragOver += DropTarget_DragOver;

    }

    private void DropTarget_DragOver(object sender, DragEventArgs e)
    {
        var vm = e.Data.GetData("Point") as PointViewModel;
        if (vm != null)
            vm.Point = e.GetPosition(this);
    }
}

Итак, ваш пример сделан с использованием 2 файлов xaml и 3 моделей представления.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...