Powiązanie obiektów zdefiniowane w kodzie

88

Mam obiekt, którego wystąpienie jest tworzone w kodzie za, na przykład, XAML nazywa się window.xaml i wewnątrz okna.xaml.cs

protected Dictionary<string, myClass> myDictionary;

Jak mogę powiązać ten obiekt, na przykład z widokiem listy, używając tylko znaczników XAML?

Aktualizacja:

(To jest dokładnie to, co mam w moim kodzie testowym):

<Window x:Class="QuizBee.Host.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="{Binding windowname}" Height="300" Width="300"
    DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Grid>
    </Grid>
</Window>

I za kodem

public partial class Window1 : Window
{
    public const string windowname = "ABCDEFG";

    public Window1()
    {
        InitializeComponent();
    }
}

Załóżmy, że tytuł powinien brzmieć „ABCDEFG”, prawda? ale ostatecznie nic nie pokazuje.

xandy
źródło
1
O dziwo, jeśli zmienię kolejność przypisania właściwości okna, to nie działa. Jeśli ustawię właściwość „Title”, po której nastąpi właściwość „DataContext”, wiązanie nie nastąpi. Czy ktoś może to wyjaśnić? <Window x: Class = "INotifyPropertyTest.MainWindow" xmlns = " schemas.microsoft.com/winfx/2006/xaml/presentation " xmlns: x = " schemas.microsoft.com/winfx/2006/xaml " xmlns: local = " clr-namespace: INotifyPropertyTest "Height =" 350 "Width =" 525 "DataContext =" {Binding RelativeSource = {RelativeSource self}} "Title =" {Binding WindowName} ">
Ramesh

Odpowiedzi:

109

Możesz ustawić DataContext dla kontrolki, formularza itp. W następujący sposób:

DataContext="{Binding RelativeSource={RelativeSource Self}}"

Wyjaśnienie :

Kontekst danych ustawiony na powyższą wartość powinien być wykonany w jakimkolwiek elemencie „jest właścicielem” kodu znajdującego się za nim - więc w przypadku okna należy ustawić to w deklaracji Window.

Mam twój przykład pracy z tym kodem:

<Window x:Class="MyClass"
  Title="{Binding windowname}"
  DataContext="{Binding RelativeSource={RelativeSource Self}}"
  Height="470" Width="626">

DataContext ustawiony na tym poziomie jest następnie dziedziczony przez dowolny element w oknie (chyba że jawnie zmienisz go dla elementu podrzędnego), więc po ustawieniu DataContext dla okna powinieneś być w stanie wykonać proste powiązanie z właściwościami CodeBehind z dowolnej kontrolki na oknie.

Guy Starbuck
źródło
1
„Self” oznacza tutaj kontrolę, a nie całą klasę okna, prawda?
xandy
Dziwne, poniżej jest kod, który mam i nie działa zgodnie z oczekiwaniami: publiczna klasa częściowa Window1: Window {public const string windowname = "ABCDEFG"; public Window1 () {InitializeComponent (); }} <Window x: Class = "QuizBee.Host.Window1" xmlns = " schemas.microsoft.com/winfx/2006/xaml/presentation " xmlns: x = " schemas.microsoft.com/winfx/2006/xaml " Tytuł = "{Binding nazwa okna}" Wysokość = "300" Szerokość = "300" DataContext = "{Binding RelativeSource = {RelativeSource Self}}"> </Window>
xandy,
10
Och, teraz jest w porządku, zmieniłem nazwę okna na właściwość zamiast czystej zmiennej publicznej i teraz może być wyświetlana! dzięki!
xandy
1
Nie mogę sobie wyobrazić, dlaczego nie jest to ustawione domyślnie.
Okonomiyaki3000
122

Jest na to znacznie łatwiejszy sposób. Możesz przypisać nazwę do swojego okna lub UserControl, a następnie powiązać przez ElementName.

Window1.xaml

<Window x:Class="QuizBee.Host.Window1"
        x:Name="Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <ListView ItemsSource="{Binding ElementName=Window1, Path=myDictionary}" />
</Window>

Window1.xaml.cs

public partial class Window1:Window
{
    // the property must be public, and it must have a getter & setter
    public Dictionary<string, myClass> myDictionary { get; set; }

    public Window1()
    {
        // define the dictionary items in the constructor
        // do the defining BEFORE the InitializeComponent();

        myDictionary = new Dictionary<string, myClass>()
        {
            {"item 1", new myClass(1)},
            {"item 2", new myClass(2)},
            {"item 3", new myClass(3)},
            {"item 4", new myClass(4)},
            {"item 5", new myClass(5)},
        }; 

        InitializeComponent();
    }
}
Saad Imran.
źródło
3
Musiałem zmienić x: Name (błąd kompilatora CS0542). Następnie należy odpowiednio zmienić ElementName.
Jack Miller,
25

Chociaż odpowiedź Guya jest poprawna (i prawdopodobnie pasuje do 9 na 10 przypadków), warto zauważyć, że jeśli próbujesz to zrobić z kontrolki, która ma już ustawiony DataContext na stosie, zresetujesz to po ustawieniu DataContext z powrotem do siebie:

DataContext="{Binding RelativeSource={RelativeSource Self}}"

To oczywiście spowoduje zerwanie istniejących powiązań.

W takim przypadku należy ustawić RelativeSource na kontrolce, którą próbujesz powiązać, zamiast jej elementu nadrzędnego.

tj. do powiązania z właściwościami UserControl:

Binding Path=PropertyName, 
        RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}

Biorąc pod uwagę, jak trudno jest obecnie zobaczyć, co się dzieje z wiązaniem danych, warto o tym pamiętać, nawet jeśli okaże się, że to ustawienie RelativeSource={RelativeSource Self}obecnie działa :)

CatBusStop
źródło
1
Silverlight 4 nie obsługuje FindAncestor. Jednak musisz to zrobić w ten sposób, aby zaimplementować FindAncestor zgodnie z opisem na tej stronie. http://blog.thekieners.com/2010/09/08/relativesource-binding-with-findancestor-mode-in-silverlight/
ShawnFeatherly
6

Jeszcze tylko trochę wyjaśnienia: właściwość bez „get”, „set” nie będzie mogła być związana

Stoję przed tą sprawą tak jak w przypadku pytającego. I muszę mieć następujące rzeczy, aby bind działał poprawnie:

//(1) Declare a property with 'get','set' in code behind
public partial class my_class:Window {
  public String My_Property { get; set; }
  ...

//(2) Initialise the property in constructor of code behind
public partial class my_class:Window {
  ...
  public my_class() {
     My_Property = "my-string-value";
     InitializeComponent();
  }

//(3) Set data context in window xaml and specify a binding
<Window ...
DataContext="{Binding RelativeSource={RelativeSource Self}}">
  <TextBlock Text="{Binding My_Property}"/>
</Window>
jondinham
źródło
9
Jak dokładnie można mieć właściwość bez funkcji „pobierz” i „ustaw”? Czy nie byłoby to pole, a nie własność?
kjbartel
1

Zdefiniuj konwerter:

public class RowIndexConverter : IValueConverter
{
    public object Convert( object value, Type targetType,
                           object parameter, CultureInfo culture )
    {
        var row = (IDictionary<string, object>) value;
        var key = (string) parameter;
        return row.Keys.Contains( key ) ? row[ key ] : null;
    }

    public object ConvertBack( object value, Type targetType,
                               object parameter, CultureInfo culture )
    {
        throw new NotImplementedException( );
    }
}

Powiąż z niestandardową definicją słownika. Pominęłam wiele przesłonięć, ale indeksator jest ważny, ponieważ emituje zdarzenie zmiany właściwości, gdy wartość zostanie zmieniona. Jest to wymagane w przypadku powiązania źródła do celu.

public class BindableRow : INotifyPropertyChanged, IDictionary<string, object>
{
    private Dictionary<string, object> _data = new Dictionary<string, object>( );

    public object Dummy   // Provides a dummy property for the column to bind to
    {
        get
        {
            return this;
        }
        set
        {
            var o = value;
        }
    }


    public object this[ string index ]
    {
        get
        {
            return _data[ index ];
        }
        set
        {
            _data[ index ] = value;
            InvokePropertyChanged( new PropertyChangedEventArgs( "Dummy" ) ); // Trigger update
        }
    }


}

W pliku .xaml użyj tego konwertera. Najpierw odwołaj się do tego:

<UserControl.Resources>
    <ViewModelHelpers:RowIndexConverter x:Key="RowIndexConverter"/>
</UserControl.Resources>

Następnie, na przykład, jeśli twój słownik ma wpis, w którym kluczem jest "Nazwa", to aby się z nim powiązać: użyj

<TextBlock  Text="{Binding Dummy, Converter={StaticResource RowIndexConverter}, ConverterParameter=Name}">
Phillip Ngan
źródło
1

Ustaw właściwość „nazwa okna” na DependencyProperty, a pozostałe zachowaj bez zmian.

Viky
źródło
0

W swoim kodzie za ustawieniem DataContext okna na słownik. W swoim XAML możesz napisać:

<ListView ItemsSource="{Binding}" />

Spowoduje to powiązanie ListView ze słownikiem.

W przypadku bardziej złożonych scenariuszy byłby to podzbiór technik leżących u podstaw wzorca MVVM .

Szymon Rozga
źródło
0

Jednym ze sposobów byłoby utworzenie ObservableCollection (System.Collections.ObjectModel) i umieszczenie w nim danych ze słownika. Następnie powinieneś być w stanie powiązać ObservableCollection z ListBox.

W swoim XAML powinieneś mieć coś takiego:

<ListBox ItemsSource="{Binding Path=Name_of_your_ObservableCollection" />
Częściowy
źródło
0

Miałem dokładnie ten sam problem, ale mój nie był, ponieważ ustawiałem zmienną lokalną ... Byłem w oknie podrzędnym i musiałem ustawić względny DataContext, który właśnie dodałem do XAML okna.

<Window x:Class="Log4Net_Viewer.LogItemWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    DataContext="{Binding RelativeSource={RelativeSource Self}}"
    Title="LogItemWindow" Height="397" Width="572">
davesbrain
źródło
0

Możesz spróbować sztuczki x: Reference

<Window ... x:Name="myWindow"><ListBox ItemsSource="{Binding Items, Source={x:Reference myWindow}}" /></Window>
Николай Солдаткин
źródło
0

To mój sposób na powiązanie z kodem za (patrz właściwość DataTemplateSelector)

public partial class MainWindow : Window
{
  public MainWindow()
  {
    this.DataTemplateSelector = new MyDataTemplateSelector();

    InitializeComponent();

    // ... more initializations ...
  }

  public DataTemplateSelector DataTemplateSelector { get; }

  // ... more code stuff ...
}

W języku XAML będzie odwoływać się za RelativeSourcepośrednictwem Ancestors do zawierającego Window, więc jestem w mojej Windowklasie i używam właściwości za pośrednictwem Pathdeklaracji:

<GridViewColumn Header="Value(s)"
                CellTemplateSelector="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataTemplateSelector}"/>

Ustawienie właściwości DataTemplateSelectorprzed wywołaniem InitializeComponentzależy od braku implementacji IPropertyChangedlub użycia implementacji, a DependencyPropertywięc nie ma komunikacji przy zmianie właściwości DataTemplateSelector.

VBWebProfi
źródło