none
[WPF]自定义控件中如果想要自定义集合属性该怎么办? RRS feed

  • 问题

  • 自己继承Window写了一个自定义的窗口,然后想要管理窗口最上方的菜单栏,这样就需要一个集合来存储菜单项。之前仅仅接触过类似Menu此类的控件,它们继承了ItemsControl,拥有Items属性,所以我也想要自定义一个类似Items的属性,但是不知道该怎么初始化它。有没有人做过类似的功能,求指教!还请告诉我如何在模版中绑定此元素,谢谢!

     public ItemCollection TopBarMenuItem
    {
                get { return (ItemCollection)GetValue(TopBarMenuItemProperty); }
                set { SetValue(TopBarMenuItemProperty, value); }
    }
    
            public static readonly DependencyProperty TopBarMenuItemProperty =
                DependencyProperty.Register(nameof(TopBarMenuItem), typeof(ItemCollection), _ownerType);

    2018年1月9日 6:51

答案

  • 参考例子

    https://stackoverflow.com/questions/7321710/wpf-binding-collection-property-in-usercontrol

    You would have to expose two separate properties, much like an ItemsControl which has an Items and ItemsSource property. It looks like you want to be able to add items using a binding and explicitly by adding to your collection. This behavior would differ from an ItemsControl, which only allows you to use the Items or ItemsSource property, but not both at the same time.

    There is nothing preventing you from adding support for both ways of specifying items though, but it would be more work on your part.

    First, you'd need a DependencyProperty, such as IEnumerable QuestionsSource, which you could bind to:

    public readonly static DependencyProperty QuestionsSourceProperty =
        DependencyProperty.Register("QuestionsSource",
            typeof(IEnumerable),
            typeof(FormQuestionReportViewer), 
            new PropertyMetadata(null));
    
     public IEnumerable QuestionsSource
     {
        get { return GetValue(QuestionsSourceProperty) as IEnumerable; }
        set { SetValue(QuestionsSourceProperty, value); }
     }

    second, you would need a regular CLR property, such as ObservableCollection<Question> Questions, which you could add items to explicitly:

    private ObservableCollection<Question> questions = new ObservableCollection<Question>();
    public ObservableCollection<Question> Questions
     {
        get { return questions; }
     }

    Then you could use these properties like so:

    <custom:CustomControl QuestionsSource="{Binding Path=Questions}">
        <custom:CustomControl.Questions>
            <custom:Question FullName="{binding Name}" ShortName="{binding ShortName}" RecOrder="{binding RecOrder}" Answer={binding Answer}" /> 
        </custom:CustomControl.Questions>          
    </custom:CustomControl>

    The extra work comes when you want to get the full list of items. You'd need to union the two collections into a single collection. This unified collection would be exposed as a third property, which returns a read-only collection.


    专注于.NET ERP/CRM开发框架,C/S架构,SQL Server + ORM(LLBL Gen Pro) + Infragistics WinForms

    2018年1月10日 1:14
  • https://stackoverflow.com/questions/12528286/binding-to-user-controls-collection-property-items-in-xaml


    The problem here is, that the Binding to ElementName=tb1 can not be resolved, even it will never be evaluated. A Binding to an ElementName is resolved for DependencyObjects which are in the visual or logical Tree of a WPF Application. Adding items to your ObservableCollection (MyList) only means adding the items to the Collection, but not into the Visual Tree.

    Edit: Here is the approach discussed in the comments:

    In your Window/Page:

    <Window.Resources>
        <!-- Declare the ViewModel as Resource -->
        <my:ViewModel x:Key="viewModel">
            <my:ViewModel.MyList>
                <my:Test A="Hello sweet" />
                <my:Test A="ViewModel" />
            </my:ViewModel.MyList>
        </my:ViewModel>
    </Window.Resources>
    
    <!-- Assign Ressource as DataContext -->
    <StackPanel DataContext="{StaticResource viewModel}">
    
        <TextBox x:Name="tb1" Text="def"/>
    
        <!-- Reference your list within the ViewModel -->
        <ListBox ItemsSource="{Binding Path=MyList}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <!-- Bind your property  -->
                    <TextBlock Text="{Binding Path=A}" />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </StackPanel>

    And the implementation of ViewModel:

    public class ViewModel
    {
        public ViewModel()
        {
            this.MyList = new ObservableCollection<Test>();
        }
    
        public ObservableCollection<Test> MyList { get; set; }
    }

    Of course, class Test no longer needs to implement a DependencyObject. Simple get/set Properties are okay.


    专注于.NET ERP/CRM开发框架,C/S架构,SQL Server + ORM(LLBL Gen Pro) + Infragistics WinForms

    2018年1月10日 1:16

全部回复

  • 参考例子

    https://stackoverflow.com/questions/7321710/wpf-binding-collection-property-in-usercontrol

    You would have to expose two separate properties, much like an ItemsControl which has an Items and ItemsSource property. It looks like you want to be able to add items using a binding and explicitly by adding to your collection. This behavior would differ from an ItemsControl, which only allows you to use the Items or ItemsSource property, but not both at the same time.

    There is nothing preventing you from adding support for both ways of specifying items though, but it would be more work on your part.

    First, you'd need a DependencyProperty, such as IEnumerable QuestionsSource, which you could bind to:

    public readonly static DependencyProperty QuestionsSourceProperty =
        DependencyProperty.Register("QuestionsSource",
            typeof(IEnumerable),
            typeof(FormQuestionReportViewer), 
            new PropertyMetadata(null));
    
     public IEnumerable QuestionsSource
     {
        get { return GetValue(QuestionsSourceProperty) as IEnumerable; }
        set { SetValue(QuestionsSourceProperty, value); }
     }

    second, you would need a regular CLR property, such as ObservableCollection<Question> Questions, which you could add items to explicitly:

    private ObservableCollection<Question> questions = new ObservableCollection<Question>();
    public ObservableCollection<Question> Questions
     {
        get { return questions; }
     }

    Then you could use these properties like so:

    <custom:CustomControl QuestionsSource="{Binding Path=Questions}">
        <custom:CustomControl.Questions>
            <custom:Question FullName="{binding Name}" ShortName="{binding ShortName}" RecOrder="{binding RecOrder}" Answer={binding Answer}" /> 
        </custom:CustomControl.Questions>          
    </custom:CustomControl>

    The extra work comes when you want to get the full list of items. You'd need to union the two collections into a single collection. This unified collection would be exposed as a third property, which returns a read-only collection.


    专注于.NET ERP/CRM开发框架,C/S架构,SQL Server + ORM(LLBL Gen Pro) + Infragistics WinForms

    2018年1月10日 1:14
  • https://stackoverflow.com/questions/12528286/binding-to-user-controls-collection-property-items-in-xaml


    The problem here is, that the Binding to ElementName=tb1 can not be resolved, even it will never be evaluated. A Binding to an ElementName is resolved for DependencyObjects which are in the visual or logical Tree of a WPF Application. Adding items to your ObservableCollection (MyList) only means adding the items to the Collection, but not into the Visual Tree.

    Edit: Here is the approach discussed in the comments:

    In your Window/Page:

    <Window.Resources>
        <!-- Declare the ViewModel as Resource -->
        <my:ViewModel x:Key="viewModel">
            <my:ViewModel.MyList>
                <my:Test A="Hello sweet" />
                <my:Test A="ViewModel" />
            </my:ViewModel.MyList>
        </my:ViewModel>
    </Window.Resources>
    
    <!-- Assign Ressource as DataContext -->
    <StackPanel DataContext="{StaticResource viewModel}">
    
        <TextBox x:Name="tb1" Text="def"/>
    
        <!-- Reference your list within the ViewModel -->
        <ListBox ItemsSource="{Binding Path=MyList}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <!-- Bind your property  -->
                    <TextBlock Text="{Binding Path=A}" />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </StackPanel>

    And the implementation of ViewModel:

    public class ViewModel
    {
        public ViewModel()
        {
            this.MyList = new ObservableCollection<Test>();
        }
    
        public ObservableCollection<Test> MyList { get; set; }
    }

    Of course, class Test no longer needs to implement a DependencyObject. Simple get/set Properties are okay.


    专注于.NET ERP/CRM开发框架,C/S架构,SQL Server + ORM(LLBL Gen Pro) + Infragistics WinForms

    2018年1月10日 1:16
  • Thanks!

    很详细,终于了却了我一个心结!

    2018年1月10日 5:35