2017-03-01 97 views
0

我是一個使用WPF的新手,但非常像在視圖模型中編寫視圖代碼和xaml以及支持代碼的想法。我想要做的就是擴展Canvas的使用,方法是將它與狀態欄關聯起來,狀態欄根據Canvas的內容和鼠標位置(下面的程式化代碼不包含此內容)顯示狀態文本。如何訪問WPF UserControl中的Canvas?

我的方法是創建一個包含Canvas並將ContentPresenter放入其中的UserControl,每個https://www.codeproject.com/Articles/82464/How-to-Embed-Arbitrary-Content-in-a-WPF-Control

我有兩個問題需要解決: 1)我需要做什麼才能以Canvas允許多個子控件的相同方式允許多個子控件? 2)如何從主窗口代碼訪問Canvas的屬性,例如Canvas.Left?

在此先感謝您的任何建議。

用戶控件XAML代碼,用戶控件的代碼後面,主窗口XAML代碼:背後

<UserControl x:Class="SO.CanvasUserControl" 
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:local="clr-namespace:SO" 
      mc:Ignorable="d" 
      d:DesignHeight="300" d:DesignWidth="300"> 
    <UserControl.Template> 
    <ControlTemplate TargetType="{x:Type local:CanvasUserControl}"> 
     <Canvas Width="200" Height="100" Background="Green"> 
     <ContentPresenter/> 
     </Canvas> 
    </ControlTemplate> 
    </UserControl.Template> 
</UserControl> 

代碼:

public partial class CanvasUserControl : UserControl 
    { 
    public CanvasUserControl() 
    { 
     InitializeComponent(); 
    } 
    } 

主窗口:

<Window x:Class="SO.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
     xmlns:local="clr-namespace:SO" 
     mc:Ignorable="d" 
     Title="MainWindow" Height="350" Width="525"> 
    <Grid> 

    <!-- works as expected 
     <Canvas Width="200" Height="100" Background="Green"> 
     <Line X1="0" Y1="0" X2="200" Y2="100" Stroke="Red"/> 
     <Line X1="200" Y1="0" X2="0" Y2="100" Stroke="Red"/> 
    </Canvas> 
    --> 

    <!-- works as expected 
    <Canvas Width="200" Height="100" Background="Green" x:Name="MyCanvas"> 
     <Line X1="{Binding ElementName=MyCanvas, Path=Left}" Y1="{Binding ElementName=MyCanvas, Path=Top}" X2="{Binding ElementName=MyCanvas, Path=ActualWidth}" Y2="{Binding ElementName=MyCanvas, Path=ActualHeight}" Stroke="Red"/> 
     <Line X1="{Binding ElementName=MyCanvas, Path=ActualWidth}" Y1="{Binding ElementName=MyCanvas, Path=Top}" X2="{Binding ElementName=MyCanvas, Path=Left}" Y2="{Binding ElementName=MyCanvas, Path=ActualHeight}" Stroke="Red"/> 
    </Canvas> 
    --> 


    <!-- How do I add more than one child control as nested content for the Canvas? 
    <local:CanvasUserControl x:Name="MyCanvasUserControl"> 
     <Line X1="0" Y1="0" X2="200" Y2="100" Stroke="Red"/> 
     <Line X1="200" Y1="0" X2="0" Y2="100" Stroke="Green"/> 
    </local:CanvasUserControl> 
    --> 

    <!-- How do I access dependency properties of the Canvas? 
    <local:CanvasUserControl x:Name="MyCanvasUserControl"> 
     <Line X1="{Binding ElementName=MyCanvasUserControl, Path=Left}" Y1="{Binding ElementName=MyCanvasUserControl, Path=Top}" X2="{Binding ElementName=MyCanvasUserControl, Path=ActualWidth}" Y2="{Binding ElementName=MyCanvasUserControl, Path=ActualHeight}" Stroke="Red"/> 
    </local:CanvasUserControl> 
    --> 

    </Grid> 
</Window> 
+0

'UserControl'繼承自'ContentControl'。它有一個'Object'屬性,類型爲'Object'。我不確定你真正想要的是一個'UserControl'。你是否希望能夠將Canvas的一些屬性捲成某種捆綁包,並將它們一起重用?這可以很容易地完成,但我會用Style來做。 –

+0

我想要做的是創建一個類似Canvas的可重用控件,但它下面還有一個StatusBar。我打算使用它的每個項目都會在「畫布兒童」列表中顯示任何想要的內容,例如兩條對角線。實際上,我已經獲得了所有這些代碼的工作,但厭倦了複製粘貼20到30行xaml以及ViewModel代碼,這會消耗鼠標位置並返回狀態欄文本。從我在StackOverflow以及其他網站上閱讀的背景中,UserControl似乎是一種可行的方式。 – Lars12345

+0

定義「任何想要的」。 –

回答

1

我幾乎扔了一起快速,但它工作並演示WPF中的控件子類別,以及ItemsControl定製。

請注意,沒有視圖模型。我們不需要一個,而在一個自定義控件中我們不需要一個。 Viewmodels由消費者提供;當你正在編寫一個自定義控件時,你就構成了框架。自定義控件的設計目的是在其他人的視圖中使用他的視圖模型,因此必須保留DataContext。如果你不這樣做,你會發現自己總是在發明解決方法。目標是編寫一些與標準WPF控件完全相同的東西。

StatusBarCanvas.cs

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 
using System.Windows; 
using System.Windows.Controls; 
using System.Windows.Controls.Primitives; 
using System.Windows.Input; 
using System.Windows.Media; 

namespace CanvasUserControl 
{ 
    public class StatusBarCanvas : ItemsControl 
    { 
     public StatusBarCanvas() 
     { 
     } 

     static StatusBarCanvas() 
     { 
      // This lets the framework finds the implicit style in 
      // Themes\Generic.xaml (below) 
      DefaultStyleKeyProperty.OverrideMetadata(
       typeof(StatusBarCanvas), 
       new FrameworkPropertyMetadata(typeof(StatusBarCanvas))); 
     } 

     public override void OnApplyTemplate() 
     { 
      base.OnApplyTemplate(); 

      var itemsPresenter = (ItemsPresenter)GetTemplateChild("PART_ItemsPresenter"); 

      // The ItemsPresenter isn't ready yet right now, so do this 
      // when it's Loaded. 
      itemsPresenter.Loaded += (s,e) => 
      { 
       Canvas = GetVisualChild<CustomCanvas>(itemsPresenter); 
      }; 
     } 

     #region Helpers 
     // Stolen from here: http://stackoverflow.com/a/19/424129 
     private static T GetVisualChild<T>(DependencyObject parent) where T : Visual 
     { 
      T child = default(T); 

      int numVisuals = VisualTreeHelper.GetChildrenCount(parent); 

      for (int i = 0; i < numVisuals; i++) 
      { 
       Visual v = (Visual)VisualTreeHelper.GetChild(parent, i); 

       //System.Diagnostics.Trace.WriteLine($"Visual Child {child}"); 

       child = v as T; 

       if (child == null) 
       { 
        child = GetVisualChild<T>(v); 
       } 
       if (child != null) 
       { 
        break; 
       } 
      } 
      return child; 
     } 

     #endregion Helpers 

     #region Canvas Property 
     /// <summary> 
     /// Gets the canvas control. 
     /// This is a readonly dependency property. Don't let anybody try to replace 
     /// the Canvas on us. 
     /// </summary> 
     public CustomCanvas Canvas 
     { 
      get { return (CustomCanvas)GetValue(CanvasProperty); } 
      protected set { SetValue(CanvasPropertyKey, value); } 
     } 

     internal static readonly DependencyPropertyKey CanvasPropertyKey = 
      DependencyProperty.RegisterReadOnly("Canvas", typeof(CustomCanvas), 
       typeof(StatusBarCanvas), new PropertyMetadata(null)); 

     public static readonly DependencyProperty CanvasProperty = 
      CanvasPropertyKey.DependencyProperty; 
     #endregion Canvas Property 
    } 

    public class CustomCanvas : Canvas 
    { 
     public CustomCanvas() 
     { 
      // This event is only happening for me on enter/leave. 
      // Fortunately that's not the question you're asking. 
      MouseMove += CustomCanvas_MouseMove; 
     } 

     protected void UpdateMousePosition() 
     { 
      MousePosition = Mouse.GetPosition(this); 

      System.Diagnostics.Trace. 
       WriteLine($"MouseMove {DateTime.Now} Position {MousePosition}"); 
     } 

     private void CustomCanvas_MouseMove(object sender, MouseEventArgs e) 
     { 
      UpdateMousePosition(); 
     } 

     #region MousePosition Property 
     /// <summary> 
     /// Gets the mouse position relative to the Canvas 
     /// Again, readonly. 
     /// </summary> 

     public Point MousePosition 
     { 
      get { return (Point)GetValue(MousePositionProperty); } 
      protected set { SetValue(MousePositionPropertyKey, value); } 
     } 
     internal static readonly DependencyPropertyKey MousePositionPropertyKey = 
      DependencyProperty.RegisterReadOnly("MousePosition", 
       typeof(Point), typeof(CustomCanvas), new PropertyMetadata(null)); 

     public static readonly DependencyProperty MousePositionProperty = 
      MousePositionPropertyKey.DependencyProperty; 
     #endregion MousePosition Property 
    } 
} 

主題\ Generic.xaml

<ResourceDictionary 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="clr-namespace:CanvasUserControl" 
    > 
    <Style TargetType="local:StatusBarCanvas"> 
     <Setter Property="Template"> 
      <Setter.Value> 
       <ControlTemplate TargetType="local:StatusBarCanvas"> 
        <Grid 
         Margin="{TemplateBinding Padding}" 
         > 
         <Grid.RowDefinitions> 
          <RowDefinition Height="*" /> 
          <RowDefinition Height="Auto" /> 
         </Grid.RowDefinitions> 
         <!-- The Border has no Row, so it fills the whole grid 
         behind the other controls --> 
         <Border 
          Background="{TemplateBinding Background}" 
          BorderBrush="{TemplateBinding BorderBrush}" 
          BorderThickness="{TemplateBinding BorderThickness}" 
          /> 
         <ItemsPresenter 
          x:Name="PART_ItemsPresenter" 
          Grid.Row="0" 
          /> 
         <StatusBar 
          Grid.Row="1" 
          x:Name="PART_StatusBar" 
          > 
          <StatusBarItem 
           Content="{Binding Canvas.MousePosition, RelativeSource={RelativeSource TemplatedParent}}" 
           ContentStringFormat="{}({0})" 
           /> 
         </StatusBar> 
        </Grid> 
       </ControlTemplate> 
      </Setter.Value> 
     </Setter> 
     <Setter Property="ItemsPanel"> 
      <Setter.Value> 
       <ItemsPanelTemplate> 
        <local:CustomCanvas 
         /> 
       </ItemsPanelTemplate> 
      </Setter.Value> 
     </Setter> 
    </Style> 
</ResourceDictionary> 

MainWindow.xaml

<Grid> 
    <local:StatusBarCanvas 
     x:Name="TestCanvas" 
     Width="200" 
     BorderBrush="Black" 
     BorderThickness="1" 
     > 
     <Line 
      X1="0" Y1="0" 
      X2="{Binding Canvas.ActualHeight, ElementName=TestCanvas}" 
      Y2="{Binding Canvas.ActualWidth, ElementName=TestCanvas}" 
      Stroke="DeepSkyBlue" 
      StrokeThickness="2" 
      /> 
     <Line 
      X1="20" Y1="20" 
      X2="100" 
      Y2="20" 
      Stroke="OrangeRed" 
      StrokeThickness="4" 
      /> 
     <Label 
      HorizontalAlignment="Center" 
      VerticalAlignment="Bottom" 
      Content="Foo Bar Planxty" 
      Canvas.Left="100" 
      Canvas.Top="100" 
      /> 
    </local:StatusBarCanvas> 
</Grid> 
+0

非常感謝。這正是我所期待的。 – Lars12345

+0

@ Lars12345太好了。如果它適合您,請將其標記爲答案。 –

0

也許我沒有完全理解你的問題,但它聽起來像你只想擁有一個內含預定義狀態欄的畫布。您可以通過擴展Canvas而非UserControl來輕鬆完成此操作。這是一個擴展Canvas的自定義組件,並且有一個狀態欄。

<Canvas x:Class="SO.CanvasUserControl" 
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:local="clr-namespace:SO" 
      mc:Ignorable="d" 
      d:DesignHeight="300" d:DesignWidth="300"> 
    <Border Width="525" Height="30" Background="Black" Canvas.Bottom="0"> 
     <TextBlock Foreground="White" Text="Hello world" FontSize="16" /> 
    </Border> 
</Canvas> 

現在將其添加到您的主窗口並指定您喜歡的任何孩子。您會看到狀態欄與所有孩子一起顯示。由於組件擴展了Canvas,因此可以根據需要添加儘可能多的子項,並且可以綁定到Canvas依賴項屬性。

<Window x:Class="SO.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
     xmlns:local="clr-namespace:SO" 
     mc:Ignorable="d" 
     Title="MainWindow" Height="350" Width="525"> 

    <local:CanvasUserControl x:Name="MyCanvasUserControl" > 
     <Line X1="0" Y1="0" X2="200" Y2="100" Stroke="Red"/> 
     <Line X1="200" Y1="0" X2="0" Y2="100" Stroke="Green"/> 
     <Line X1="{Binding ElementName=MyCanvasUserControl, Path=Left}" Y1="{Binding ElementName=MyCanvasUserControl, Path=Top}" X2="{Binding ElementName=MyCanvasUserControl, Path=ActualWidth}" Y2="{Binding ElementName=MyCanvasUserControl, Path=ActualHeight}" Stroke="Red"/> 
    </local:CanvasUserControl> 
</Window> 
相關問題