none
WPF MVVM биндинг ContentPresenter на вложенную вьюмодель RRS feed

  • Вопрос

  • Можно ли (желательно исключительно в разметке) во вью сделать биндинг на свойство, которое не описано в связанной (через DataContext) с ним вьюмодели(VM1)? Скажем, сделать биндинг на свойство, которое описано в другой вьюмодели (VM2), не указанной в качестве контекста для данного вью? VM2 я не могу указать в качестве контекста, так как это все происходит в детайле схемы мастер-детайл, а мастер забинден на коллекцию из VM1, и детайл, соответственно, должен отображать содержимое этой коллекции из VM1, то есть биндинг в детайле идет на эту же коллекцию из VM1.

    Не уверен, что корректно сформулировал вопрос, но просто если я начну выкладывать реальный код, там получится целая стена.

    • Изменено Qwester33 28 февраля 2012 г. 10:35
    25 февраля 2012 г. 13:18

Ответы

  • В вопорсе вы говорите о о бинденге не на вьюмодель, но если суть в том, что бы привязатся к свойству другой вьюмодели. то можно попробовать объявить VM2 в классе Application, а байдинг задать так:

    {Binding Source={x:Static Application.Current}, Path=(VM2.Свойство)}


    Влюблен в WPF Не пишу на C#

    • Помечено в качестве ответа Abolmasov Dmitry 27 февраля 2012 г. 14:36
    26 февраля 2012 г. 9:50
    Отвечающий
  • Сделайте общую MainViewModel, в которой будут 2 свойства VM1 и VM2 и используйте релативсорс. Только AncestorType будет элемент, который забайнден на MainViewModel. Получится примерно так:

    {Binding RelativeSource={RelativeSource AncestorType={x:Type MyWindow}}, Path=DataContext.VM2}"

    • Помечено в качестве ответа Abolmasov Dmitry 27 февраля 2012 г. 14:36
    26 февраля 2012 г. 20:36
    Отвечающий
  • Похоже я запутался...

    У вас есть VM1 со свойством-коллекцией VM2. В Contentcontrol вы отображаете VM1 и хотите отобразить что-то из VM2, что выбрал пользователь в листе из OdcSpisok2, так?

    Если да, то проще в VM1 сделать свойство SelectedItem и его забиндить в листе. тогда в тексте у вас будет:

    <TextBlock Text="{Binding параметр_из_VM1_биндинг_работает}"/>
    <TextBlock Text="{Binding SelectedItem.SomeField}" />

    • Помечено в качестве ответа Qwester33 28 февраля 2012 г. 10:32
    28 февраля 2012 г. 8:46
    Отвечающий
  • Вью модел и не должна о нем знать. Это должно быть просто свойство с нотификацией. А во вьюшке будет что-то такое:

    <ListBox ItemsSource="{Binding VM1.ListOfVM2}" SelectedItem="{Binding VM1.SelectedItem}"> </ListBox>

    • Помечено в качестве ответа Qwester33 28 февраля 2012 г. 10:33
    28 февраля 2012 г. 9:40
    Отвечающий

Все ответы

  • Можно задать нужно свойство в классе окна, а биндинг задать через релативсурс. То есть находите через релативсурс окно, а в качестве Path задать нужное свойство.

    Влюблен в WPF Не пишу на C#

    • Помечено в качестве ответа Qwester33 25 февраля 2012 г. 21:15
    • Снята пометка об ответе Qwester33 25 февраля 2012 г. 21:16
    25 февраля 2012 г. 18:51
    Отвечающий
  • У меня VM2 не окно, а именно вьюмодель. Возможно, из-за этого, я пробовал так, но не вышло:

    {Binding Path=свойство из VM2, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type неймспейс VM2:класс VM2}}}"

    То есть, не биндится вообще, при построении в Выводе пишет ошибку биндинга.

    • Изменено Qwester33 25 февраля 2012 г. 21:20
    25 февраля 2012 г. 21:15
  • В вопорсе вы говорите о о бинденге не на вьюмодель, но если суть в том, что бы привязатся к свойству другой вьюмодели. то можно попробовать объявить VM2 в классе Application, а байдинг задать так:

    {Binding Source={x:Static Application.Current}, Path=(VM2.Свойство)}


    Влюблен в WPF Не пишу на C#

    • Помечено в качестве ответа Abolmasov Dmitry 27 февраля 2012 г. 14:36
    26 февраля 2012 г. 9:50
    Отвечающий
  • Сделайте общую MainViewModel, в которой будут 2 свойства VM1 и VM2 и используйте релативсорс. Только AncestorType будет элемент, который забайнден на MainViewModel. Получится примерно так:

    {Binding RelativeSource={RelativeSource AncestorType={x:Type MyWindow}}, Path=DataContext.VM2}"

    • Помечено в качестве ответа Abolmasov Dmitry 27 февраля 2012 г. 14:36
    26 февраля 2012 г. 20:36
    Отвечающий
  • Что-то не получается разобраться, попробую описать свою ситуацию. Я разобрался в отношениях между VM1 и VM2, у меня оказывается VM2 вложена в VM1. Есть Spisok1, который содержит Spisok2. В итоге я должен видеть содержимое Spisok2 в каждом элементе Spisok1. Реализовано это так:

    Имеется MainViewModel, которая содержит ObservableCollection <VM1> ObcSpisok1, а далее каждый элемент ObcSpisok1 содержит ObservableCollection <VM2> ObcSpisok2. Если внутри вью отображать только содержимое соотвествующей ему вьюмодели, то все работает.

    Далее я решаю сделать мастер-детайл для списка1. В итоге код выглядит так:

        <DockPanel>
            <ListView ItemsSource="{Binding ObcSpisok1 (биндинг первого списка)>
                <ListView.ItemTemplate>
                    <DataTemplate>
                            <ListView ItemsSource="{Binding ObcSpisok2(биндинг второго списка)l}">
                            </ListView>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
    
            <ContentControl Content="{Binding ObcSpisok1 (биндинг первого списка чтобы просмотреть его детально)}">
                <ContentControl.ContentTemplate>
                    <DataTemplate>
                            <StackPanel>
                                <TextBlock Text="{Binding параметр_из_VM1_биндинг_работает}"/>
    
                                <TextBlock Text= а_вот_тут_я_хочу_забиндить_на_параметр_из_VM2_но_не_работает/>
                            </StackPanel>
                    </DataTemplate>
                </ContentControl.ContentTemplate>
            </ContentControl>
        </DockPanel>
    • Изменено Qwester33 27 февраля 2012 г. 17:34
    27 февраля 2012 г. 16:16
  • Тут потому что вы использовали шаблон, поэтому и не получается сходу сделать. Воспользуйтесь TemplatedParent:

    RelativeSource={RelativeSource Mode=TemplatedParent}

    Кстати, а что вы писали в

     а_вот_тут_я_хочу_забиндить_на_параметр_из_VM2_но_не_работает
    27 февраля 2012 г. 19:24
    Отвечающий
  • Я там пытался сделать по аналогии с работающими вариантами:

    TextBlock Text="{Binding P1, UpdateSourceTrigger=PropertyChanged}"

    и

    {Binding Path=P1, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type неймспейс VM2:класс VM2}}}"

    Сейчас я пробую:

    <TextBlock Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=P1, UpdateSourceTrigger=PropertyChanged}>

    Но получаю:

    System.Windows.Data Error: 40 : BindingExpression path error: 'P1' property not found on 'object' ''ContentPresenter' (Name='')'. BindingExpression:Path=P1; DataItem='ContentPresenter' (Name=''); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')

    То есть, P1 у меня это свойство VM2, а VM2 это вьюмодель, которая предоставляет данные для отображения элементов ObcSpisok2:

    public class VM2: ViewModelBase {

     //содержащиеся здесь свойства нормально биндятся только Внутри <ListView ItemsSource="{Binding ObcSpisok2(биндинг второго списка)l}"/>

    /// <summary> /// Параметр 1 для отображения. На это свойство мне не удается ничего забиндить в Детайле.. /// </summary> public decimal P1 { get { return R.P1; } set { R.P1 = value; OnPropertyChanged("P1"); } } }

    Код VM1:

    public class VM1: ViewModelBase { public ObservableCollection<VM2> ObcSpisok2 { get; set; }

    //содержащиеся здесь свойства нормально биндятся в Детайле в разметке простым путем, типа: TextBlock Text="{Binding свойство}"

    }

    И код главной вьюмодели:

        public class ViewModelMain : ViewModelBase
        {
            public ObservableCollection<VM1> ObcSpisok1 { get; set; }
        }


    • Изменено Qwester33 27 февраля 2012 г. 20:50
    27 февраля 2012 г. 20:33
  • А у ContentPresenter-а (у которого не находится P1) в свойстве DataContext какой объект находится?
    28 февраля 2012 г. 7:12
    Отвечающий
  • Если посмотреть в свойствах ContentPresenter, то в DataContext написано "привязка", если щелкну то открывается окно со списком источников:

    1. DataContext - пусто

    2. ElementName - указано имя вот этого списка (выделен жирным):

        <DockPanel>
            <ListView ItemsSource="{Binding ObcSpisok1 (биндинг первого списка)>
                <ListView.ItemTemplate>
                    <DataTemplate>
                            <ListView ItemsSource="{Binding ObcSpisok2(биндинг второго списка)l}">
                            </ListView>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
    
            <ContentControl Content="{Binding ObcSpisok1 (биндинг первого списка чтобы просмотреть его детально)}">
                <ContentControl.ContentTemplate>
                    <DataTemplate>
                            <StackPanel>
                                <TextBlock Text="{Binding параметр_из_VM1_биндинг_работает}"/>
    
                                <TextBlock Text= а_вот_тут_я_хочу_забиндить_на_параметр_из_VM2_но_не_работает/>
                            </StackPanel>
                    </DataTemplate>
                </ContentControl.ContentTemplate>
            </ContentControl>
        </DockPanel>
    3. и 4. наверное неважно.

    • Изменено Qwester33 28 февраля 2012 г. 8:29
    28 февраля 2012 г. 8:28
  • Похоже я запутался...

    У вас есть VM1 со свойством-коллекцией VM2. В Contentcontrol вы отображаете VM1 и хотите отобразить что-то из VM2, что выбрал пользователь в листе из OdcSpisok2, так?

    Если да, то проще в VM1 сделать свойство SelectedItem и его забиндить в листе. тогда в тексте у вас будет:

    <TextBlock Text="{Binding параметр_из_VM1_биндинг_работает}"/>
    <TextBlock Text="{Binding SelectedItem.SomeField}" />

    • Помечено в качестве ответа Qwester33 28 февраля 2012 г. 10:32
    28 февраля 2012 г. 8:46
    Отвечающий
  • Вы совершенно точно сформулировали задачу. Про биндинг понял, я попробовал возвращать конкретный элемент, и принципиально все работает, спасибо за помощь. Осталось разобраться, как возвращать выбранный пользователем элемент.

    public VM2 SelectedItem { get { return ObcSpisok2.LastOrDefault();

                    А как тут узнать, какой элемент второго списка выбран и получить его? Это же вьюмодель и я не имею прямого доступа к вью.
    Есть доступ только к ObcSpisok2, на который забинден второй список

    } }

    • Изменено Qwester33 28 февраля 2012 г. 9:48
    28 февраля 2012 г. 9:28
  • Вью модел и не должна о нем знать. Это должно быть просто свойство с нотификацией. А во вьюшке будет что-то такое:

    <ListBox ItemsSource="{Binding VM1.ListOfVM2}" SelectedItem="{Binding VM1.SelectedItem}"> </ListBox>

    • Помечено в качестве ответа Qwester33 28 февраля 2012 г. 10:33
    28 февраля 2012 г. 9:40
    Отвечающий
  • Спасибо, разобрался. Название темы тоже изменил, так как вначале я коряво и неверно сформулировал проблему.

    ViewModelMain:

        public class ViewModelMain : ViewModelBase
        {
            public ObservableCollection<VM1> ObcSpisok1 { get; set; }
        }

    VM1:

    public class VM1: ViewModelBase { public ObservableCollection<VM2> ObcSpisok2 { get; set; }

            private VM2 selectedItem;
            public VM2 SelectedItem
            {
                get { return selectedItem; }
                set
                {
                    selectedItem = value;
                    OnPropertyChanged("SelectedItem");
                }
            }

    }

    VM2:

        public class VM2: ViewModelBase
        {
            public decimal P1
            {
                get { return источник_в_модели.P1; }
                set
                {
                    источник_в_модели.P1 = value;
                    OnPropertyChanged("P1");
                }
            }
        }

    Код разметки:

        <DockPanel>
            <ListView ItemsSource="{Binding ObcSpisok1 (биндинг первого списка)>
                <ListView.ItemTemplate>
                    <DataTemplate>
                            <ListView ItemsSource="{Binding ObcSpisok2(биндинг второго списка)l} SelectedItem="{Binding SelectedItem}">
                            </ListView>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
    
            <ContentControl Content="{Binding ObcSpisok1 (биндинг первого списка чтобы просмотреть его детально)}">
                <ContentControl.ContentTemplate>
                    <DataTemplate>
                            <StackPanel>
                                <TextBlock Text="{Binding SelectedItem.P1, UpdateSourceTrigger=PropertyChanged}"/>
                            </StackPanel>
                    </DataTemplate>
                </ContentControl.ContentTemplate>
            </ContentControl>
        </DockPanel>
    • Изменено Qwester33 28 февраля 2012 г. 10:56
    28 февраля 2012 г. 10:32