2010-09-07 39 views
40

Điều này nghe có vẻ đơn giản. Tôi có một số Page được khai báo trong XAML theo cách thông thường (tức là với "Thêm mục mới ...") và nó có thuộc tính tùy chỉnh. Tôi muốn đặt thuộc tính đó trong XAML được liên kết với trang.Đặt thuộc tính tùy chỉnh trong trang WPF/Silverlight

Cố gắng làm điều này theo cùng cách mà tôi muốn đặt bất kỳ thuộc tính nào khác không hoạt động, vì lý do tôi hiểu nhưng không biết cách làm việc vòng. Vì vậy, chúng tôi đã có một cái gì đó cụ thể để nói về, đây là một số (không hợp lệ) XAML. Tôi đã giảm tất cả mọi thứ xuống càng nhiều càng tốt - ban đầu có những thuộc tính như kích thước thiết kế, nhưng tôi tin rằng những thứ đó không liên quan đến những gì tôi đang cố gắng làm.

<Page x:Class="WpfSandbox.TestPage" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     MyProperty="MyPropertyValue"> 
</Page> 

và code-behind tương ứng:

using System.Windows.Controls; 

namespace WpfSandbox { 
    public partial class TestPage : Page { 
    public TestPage() { 
     InitializeComponent(); 
    } 

    public string MyProperty { get; set; } 
    } 
} 

Thông báo lỗi:

Lỗi 1 Thuộc tính 'myProperty' không tồn tại trong không gian tên XML 'http://schemas.microsoft.com/winfx/2006/xaml/presentation'. Dòng 4 Chức 7.

Bây giờ tôi biết tại sao điều này là không: nguyên tố này là loại Page, và Page không có một tài sản gọi là MyProperty. Nó chỉ được khai báo trong TestPage ... được xác định bởi thuộc tính x:Class, nhưng không phải bởi chính phần tử đó. Theo tôi biết, cấu hình này được yêu cầu bởi mô hình xử lý XAML (tức là tích hợp Visual Studio, v.v.).

Tôi nghi ngờ mình có thể xử lý điều này với thuộc tính phụ thuộc, nhưng điều đó có vẻ hơi quá mức. Tôi cũng có thể sử dụng thuộc tính hiện tại (ví dụ: DataContext) và sau đó sao chép giá trị vào thuộc tính tùy chỉnh trong mã sau, nhưng điều đó sẽ khá xấu.

Ở trên là ví dụ WPF, nhưng tôi nghi ngờ các câu trả lời tương tự sẽ áp dụng trong Silverlight. Tôi quan tâm cả hai - vì vậy nếu bạn đăng một câu trả lời mà bạn biết sẽ làm việc trong một nhưng không phải là khác, tôi sẽ biết ơn nếu bạn chỉ ra rằng trong câu trả lời :)

Tôi đang chuẩn bị tự khắc phục khi ai đó đăng một giải pháp hoàn toàn tầm thường ...

+0

"MyProperty" trong phần tử xaml 'Page' có cần một không gian tên xml không? Chẳng hạn như "' x: MyProperty' "? (Không phải theo nghĩa đen, nhưng tương tự). Điểm là nó không có trong không gian tên đó, vậy các vùng tên khác mà nó đang kiểm tra là gì? –

+1

@Filip: Tôi không tin rằng đó thực sự là một bản sao của câu hỏi đó, đang nói về các thuộc tính đính kèm. Vấn đề ở đây là thuộc tính mà tôi đang cố gắng thiết lập là một thuộc tính của lớp * thực tế * hiệu quả hơn là thuộc tính được khai báo bởi phần tử. Tôi có thể sai, tất nhiên. –

+4

Wow .. Jon Skeet đã có một cuộc bỏ phiếu gần! Những gì được thế giới sắp tới?? – Arcturus

Trả lời

30

Bạn có thể làm việc với thuộc tính bình thường mà không có thuộc tính phụ thuộc nếu bạn tạo một lớp cơ sở cho trang của bạn.

public class BaseWindow : Window 
{ 
    public string MyProperty { get; set; } 
} 

<local:BaseWindow x:Class="BaseWindowSample.Window1" x:Name="winImp" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="clr-namespace:BaseWindowSample" 
    MyProperty="myproperty value" 
    Title="Window1" Height="300" Width="300"> 

</local:BaseWindow> 

Và nó hoạt động ngay cả khi MyProperty không phải là phụ thuộc hoặc được đính kèm.

+0

Điều đó sẽ mất 'InitializeComponent()' –

+23

-1 cho táo bạo để trả lời câu hỏi jon skeet? –

+1

@Sudhir Công việc tốt. – abhishek

0

Bạn sẽ cần xác định thuộc tính có thể đính kèm để truy cập nó như thế này.

3

Bạn có thể tuyên bố yếu tố <Page> của bạn trở thành một yếu tố <TestPage> thay vì:

<YourApp:TestPage 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:YourApp="clr-namespace:YourApp" 
    MyProperty="Hello"> 
</YourApp:TestPage> 

Điều đó sẽ làm các trick, nhưng bạn bị mất InitializeComponent() và các công cụ thiết kế tiêu chuẩn. Chế độ thiết kế vẫn có vẻ hoạt động hoàn hảo, tuy nhiên, nhưng tôi chưa thử nghiệm rộng rãi điều này.

CẬP NHẬT: Việc biên dịch và chạy này, nhưng không thực sự đặt MyProperty. Bạn cũng mất khả năng ràng buộc xử lý sự kiện trong XAML (mặc dù có thể có một cách để khôi phục lại cái mà tôi không biết).

UPDATE 2: mẫu làm việc từ @Fredrik Mork mà bộ tài sản, nhưng không hỗ trợ xử lý sự kiện ràng buộc trong XAML:

Mã đằng sau:

namespace WpfApplication1 
{ 
    public partial class MainWindow : Window 
    { 
     protected override void OnActivated(EventArgs e) 
     { 
      this.Title = MyProperty; 
     }  

     public string MyProperty { get; set; } 
    } 
} 

XAML:

<WpfApplication1:MainWindow 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:WpfApplication1="clr-namespace:WpfApplication1" 
    Title="MainWindow" 
    Height="350" 
    Width="525" 
    MyProperty="My Property Value"> 
</WpfApplication1:MainWindow> 
+0

Thử điều đó, tôi nhận được "Thẻ TestPage không tồn tại trong không gian tên XML 'http://schemas.microsoft.com/winfx/2006/xaml/presentation'" Line 2 Position 7. Hmm. Tôi đã thử cho nó một không gian tên clr là tốt, và điều đó dường như không hoạt động: ( –

+0

@ Håvard, Điều đó sẽ biên dịch và chạy nhưng Tài sản không có giá trị "Xin chào". –

+0

Xin lỗi, nhẹ mispost Bạn cần 'xmlns: YourApp =" clr-namespace: YourApp "' và khai báo là ''. Cập nhật câu trả lời –

8

Bạn cần làm cho nó là attachable property như Pavel đã lưu ý, sau đó bạn có thể viết một cái gì đó như thế này

<Page x:Class="JonSkeetTest.SkeetPage" 
     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:JonSkeetTest="clr-namespace:JonSkeetTest" mc:Ignorable="d" 
     d:DesignHeight="300" d:DesignWidth="300" 
     JonSkeetTest:SkeetPage.MyProperty="testar" 
    Title="SkeetPage"> 
    <Grid> 

    </Grid> 
</Page> 

Tuy nhiên chỉ với mã này đằng sau:

Bạn sẽ nhận được lỗi này thay vì:

Thuộc tính gắn thêm 'myProperty' không được tìm thấy trong loại 'SkeetPage'.

Các tài sản gắn liền 'SkeetPage.MyProperty' không được định nghĩa vào 'Trang' hoặc một trong các lớp cơ sở của nó.


Sửa

Đáng tiếc là bạn phải sử dụng phụ thuộc Properties, cô là một ví dụ làm việc

Trang

<Page x:Class="JonSkeetTest.SkeetPage" 
     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:JonSkeetTest="clr-namespace:JonSkeetTest" mc:Ignorable="d" 
     JonSkeetTest:SkeetPage.MyProperty="Testing.." 
     d:DesignHeight="300" d:DesignWidth="300" 
    Title="SkeetPage"> 

    <Grid> 
     <Button Click="ButtonTest_Pressed"></Button> 
    </Grid> 
</Page> 

Bộ luật đằng sau

using System.Windows; 
using System.Windows.Controls; 

namespace JonSkeetTest 
{ 
    public partial class SkeetPage 
    { 
     public SkeetPage() 
     { 
      InitializeComponent(); 
     } 

     public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register(
      "MyProperty", 
      typeof(string), 
      typeof(Page), 
      new FrameworkPropertyMetadata(null, 
       FrameworkPropertyMetadataOptions.AffectsRender 
     ) 
     ); 

     public static void SetMyProperty(UIElement element, string value) 
     { 
      element.SetValue(MyPropertyProperty, value); 
     } 
     public static string GetMyProperty(UIElement element) 
     { 
      return element.GetValue(MyPropertyProperty).ToString(); 
     } 

     public string MyProperty 
     { 
      get { return GetValue(MyPropertyProperty).ToString(); } 
      set { SetValue(MyPropertyProperty, value); } 
     } 

     private void ButtonTest_Pressed(object sender, RoutedEventArgs e) 
     { 
      MessageBox.Show(MyProperty); 
     } 
    } 
} 

Nếu bạn nhấn nút, bạn sẽ thấy "Đang kiểm tra ..." trong Hộp thư.

+0

Đây là câu trả lời đúng. – l33t

2

XAML của bạn là tương đương với các nội dung sau:

<Page x:Class="SkeetProblem.TestPage" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> 
    <Page.MyProperty>MyPropertyValue</Page.MyProperty> 
</Page> 

này rõ ràng là bất hợp pháp. Các XAML-file đã được nạp theo phương pháp LoadComponent tĩnh của lớp ứng dụng, và the reference nói:

tải một tập tin XAML mà nằm ở định quy định tài nguyên thống nhất (URI) và chuyển nó đến một thể hiện của đối tượng được chỉ định bởi phần tử gốc của tệp XAML.

Điều đó có nghĩa là bạn chỉ có thể đặt thuộc tính cho loại được chỉ định bởi phần tử gốc.Vì vậy, bạn cần phải phân lớp trang và chỉ định phân lớp đó làm phần tử gốc của bạn XAML.

1

Đề nghị của tôi sẽ là một DependencyProperty với một mặc định:

public int MyProperty 
    { 
     get { return (int)GetValue(MyPropertyProperty); } 
     set { SetValue(MyPropertyProperty, value); } 
    } 

    public static readonly DependencyProperty MyPropertyProperty = 
     DependencyProperty.Register("MyProperty", typeof(int), typeof(MyClass), 
       new PropertyMetadata(1337)); //<-- Default goes here 

Xem các thuộc tính của các điều khiển như một cái gì đó bạn tiếp xúc với thế giới bên ngoài để sử dụng.

Nếu bạn muốn sử dụng thuộc tính của riêng mình, bạn có thể sử dụng ElementName hoặc RelativeSource Bindings.

Về điều quá mức cần thiết, DependencyProperties đi tay trong tay với DependencyObjects;)

Không XAML thêm cần thiết, mặc định trong PropertyMetadata sẽ làm phần còn lại.

Nếu bạn thực sự muốn đặt nó vào XAML, hãy đi đến giải pháp lớp cơ sở, hoặc các vị thần cấm, giới thiệu một thuộc tính có thể đính kèm, có thể được sử dụng trên bất kỳ điều khiển nào khác.

+0

Tôi đề nghị bạn thực sự thử điều này, nó không hoạt động. – AnthonyWJones

+0

Tại sao điều này không hoạt động? Không cần thêm XAML nữa ... Mặc định sẽ làm phần còn lại .. – Arcturus

1

Trả lời liên quan đến Silverlight.

Không có cách đơn giản rõ ràng để sử dụng tài sản đồng bằng theo cách bạn muốn, sẽ phải có một số thỏa hiệp trên đường đi.

Không thực sự làm việc: -

Một số người cho một tài sản phụ thuộc. Điều đó sẽ không hoạt động, nó vẫn là một tài sản công cộng từ Xaml POV. Một thuộc tính đính kèm sẽ hoạt động nhưng điều đó sẽ làm việc với nó trong mã xấu xí.

Đóng nhưng không có chuối: -

Các XAML và các lớp có thể được tách ra hoàn toàn như thế này: -

<local:PageWithProperty 
      xmlns:local="clr-namespace:StackoverflowSpikes" 
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
      xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation" 
    Message="Hello World" 
    Loaded="PageWithProperty_Loaded" 
    Title="Some Title" 
      > 
    <Grid x:Name="LayoutRoot"> 
     <TextBlock Text="{Binding Parent.Message, ElementName=LayoutRoot}" /> 
    </Grid> 
</local:PageWithProperty> 

Code: -

public class PageWithProperty : Page 
{ 

     internal System.Windows.Controls.Grid LayoutRoot; 

     private bool _contentLoaded; 

     public void InitializeComponent() 
     { 
      if (_contentLoaded) { 
       return; 
      } 
      _contentLoaded = true; 
      System.Windows.Application.LoadComponent(this, new System.Uri("/StackoverflowSpikes;component/PageWithProperty.xaml", System.UriKind.Relative)); 
      this.LayoutRoot = ((System.Windows.Controls.Grid)(this.FindName("LayoutRoot"))); 
     } 

    public PageWithProperty() 
    { 
     InitializeComponent(); 
    } 

    void PageWithProperty_Loaded(object sender, RoutedEventArgs e) 
    { 
     MessageBox.Show("Hi"); 
    } 
    public string Message {get; set; } 

} 

Tuy nhiên bạn sẽ mất một số hỗ trợ từ nhà thiết kế. Đáng chú ý là bạn sẽ phải tạo các trường để giữ các tham chiếu đến các phần tử được đặt tên và tự gán chúng trong việc thực hiện của riêng bạn InitialiseComponent (IMO tất cả các trường tự động cho các mục được đặt tên này không nhất thiết phải là một điều tốt). Ngoài ra, nhà thiết kế sẽ không tạo mã sự kiện động cho bạn (mặc dù có vẻ lạ khi biết cách điều hướng đến một trong những bạn đã tạo theo cách thủ công) tuy nhiên các sự kiện được định nghĩa trong Xaml sẽ được kết nối khi chạy.

IMO lựa chọn tốt nhất: -

Các thỏa hiệp tốt nhất đã được đăng bởi Abhishek, sử dụng một lớp cơ sở shim để giữ tài sản. Nỗ lực Minimul, khả năng tương thích tối đa.

1

Tôi chỉ cố gắng làm tương tự với một số ý định khác nhau.

Câu trả lời thực sự là: Bạn cần quy ước WPF cho các phương thức Set được thực hiện đúng. Như đã nêu ở đây: http://msdn.microsoft.com/en-us/library/ms749011.aspx#custom bạn phải xác định phương thức SetXxx và GetXxx nếu bạn định xóa thuộc tính đính kèm có tên Xxx.

Vì vậy, xem ví dụ này làm việc:

public class Lokalisierer : DependencyObject 
{ 
    public Lokalisierer() 
    { 
    } 

    public static readonly DependencyProperty LIdProperty = 
     DependencyProperty.RegisterAttached("LId", 
              typeof(string), 
              typeof(Lokalisierer), 
              new FrameworkPropertyMetadata( 
                null, 
                FrameworkPropertyMetadataOptions.AffectsRender | 
                FrameworkPropertyMetadataOptions.AffectsMeasure, 
                new PropertyChangedCallback(OnLocIdChanged))); 

    private static void OnLocIdChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
    // on startup youll be called here 
    } 

    public static void SetLId(UIElement element, string value) 
    { 
     element.SetValue(LIdProperty, value); 
    } 
    public static string GetLId(UIElement element) 
    { 
     return (string)element.GetValue(LIdProperty); 
    } 


    public string LId 
    { 
     get{ return (string)GetValue(LIdProperty); } 
     set{ SetValue(LIdProperty, value); } 
    } 
} 

Và phần WPF:

<Window x:Class="LokalisierungMitAP.Window1" 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
xmlns:me="clr-namespace:LokalisierungMitAP" 
Title="LokalisierungMitAP" Height="300" Width="300" 
> 
<StackPanel> 
    <Label me:Lokalisierer.LId="hhh">Label1</Label> 
    </StackPanel> 

BTW: Bạn cũng cần phải kế thừa DependencyObject

1

này làm việc cho tôi

<Window x:Class="WpfSandbox.MainWindow" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="clr-namespace:WpfSandbox"   
    xmlns:src="clr-namespace:WpfSandbox" 
    Title="MainWindow" Height="350" Width="525" 
    src:MainWindow.SuperClick="SuperClickEventHandler"> 
</Window> 

Vì vậy, điều này có thể phù hợp với câu hỏi ban đầu (không thử). Lưu ý xmlns: src.

<Page x:Class="WpfSandbox.TestPage" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="clr-namespace:WpfSandbox"   
    xmlns:src="clr-namespace:WpfSandbox" 
    src:TestPage.MyProperty="MyPropertyValue"> 
</Page> 
Các vấn đề liên quan