Objetos de vinculação definidos em code-behind

88

Eu tenho algum objeto que é instanciado no código por trás, por exemplo, o XAML é chamado de window.xaml e dentro de window.xaml.cs

protected Dictionary<string, myClass> myDictionary;

Como posso vincular este objeto a, por exemplo, uma exibição de lista, usando apenas marcações XAML?

Atualizar:

(Isso é exatamente o que tenho em meu código de teste):

<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>

E no codebehind

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

    public Window1()
    {
        InitializeComponent();
    }
}

Suponha que o título se torne "ABCDEFG" certo? mas acaba não mostrando nada.

xandy
fonte
1
Estranhamente, se eu alterar a ordem da atribuição de propriedade da janela, isso não funciona. Se eu definir a propriedade "Título" seguida pela propriedade "DataContext", a vinculação não acontecerá. Alguém pode explicar isso? <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

Respostas:

109

Você pode definir o DataContext para seu controle, formulário, etc., assim:

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

Esclarecimento :

O contexto de dados sendo definido para o valor acima deve ser feito em qualquer elemento "proprietário" do código por trás - portanto, para uma janela, você deve defini-lo na declaração de janela.

Eu tenho seu exemplo trabalhando com este código:

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

O DataContext definido neste nível é herdado por qualquer elemento na janela (a menos que você o altere explicitamente para um elemento filho), portanto, após definir o DataContext para a janela, você deve ser capaz de fazer apenas a vinculação direta às propriedades CodeBehind de qualquer controle na janela.

Guy Starbuck
fonte
1
O "Self" aqui significa o controle, em vez de toda a classe de janela, não é?
xandy
Estranho o suficiente, a seguir está o código que eu tenho e ele não funciona como esperado: public partial class 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 " Título = "{Binding windowname}" Height = "300" Width = "300" DataContext = "{Binding RelativeSource = {RelativeSource Self}}"> </Window>
xandy
9
Ah, está tudo bem agora, mudei o nome da janela para propriedade em vez de variável pública pura e ele pode ser exibido agora! obrigado!
xandy de
1
Não consigo imaginar por que isso não é definido apenas por padrão.
Okonomiyaki3000,
122

Existe uma maneira muito mais fácil de fazer isso. Você pode atribuir um nome à sua janela ou UserControl e, em seguida, vincular por 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.
fonte
3
Tive que alterar x: Name (erro do compilador CS0542). Então ElementName precisa ser alterado de acordo.
Jack Miller
25

Embora a resposta de Guy esteja correta (e provavelmente se encaixe em 9 de 10 casos), vale a pena observar que se você estiver tentando fazer isso a partir de um controle que já tem seu DataContext configurado mais acima na pilha, você redefinirá isso quando definir DataContext de volta a si mesmo:

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

Isso, é claro, quebrará suas ligações existentes.

Se for esse o caso, você deve definir o RelativeSource no controle que está tentando vincular, em vez de seu pai.

ou seja, para vincular às propriedades de um UserControl:

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

Considerando o quão difícil pode ser atualmente ver o que está acontecendo com a vinculação de dados, vale a pena ter isso em mente, mesmo se você achar que essa configuração RelativeSource={RelativeSource Self}atualmente funciona :)

CatBusStop
fonte
1
O Silverlight 4 não oferece suporte a FindAncestor. No entanto, você precisa fazer desta maneira, você pode implementar FindAncestor conforme descrito neste site. http://blog.thekieners.com/2010/09/08/relativesource-binding-with-findancestor-mode-in-silverlight/
ShawnFeatherly
7

Só mais um esclarecimento: uma propriedade sem 'get', 'set' não poderá ser vinculada

Estou enfrentando o caso exatamente como o caso do autor da pergunta. E eu devo ter os seguintes itens para que a ligação funcione corretamente:

//(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
fonte
9
Como exatamente você pode ter uma propriedade sem 'obter' e 'definir'? Não seria um campo e não uma propriedade?
kjbartel
1

Defina um conversor:

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( );
    }
}

Vincule a uma definição personalizada de um Dicionário. Omiti muitas substituições, mas o indexador é o mais importante, porque ele emite o evento de alteração da propriedade quando o valor é alterado. Isso é necessário para a ligação de origem a destino.

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
        }
    }


}

Em seu arquivo .xaml, use este conversor. Primeira referência:

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

Então, por exemplo, se o seu dicionário tem uma entrada onde a chave é "Nome", então para vincular a ela: use

<TextBlock  Text="{Binding Dummy, Converter={StaticResource RowIndexConverter}, ConverterParameter=Name}">
Phillip Ngan
fonte
1

Transforme sua propriedade "windowname" em DependencyProperty e mantenha o restante.

viky
fonte
0

Em seu code behind, defina o DataContext da janela para o dicionário. Em seu XAML, você pode escrever:

<ListView ItemsSource="{Binding}" />

Isso vinculará o ListView ao dicionário.

Para cenários mais complexos, este seria um subconjunto de técnicas por trás do padrão MVVM .

Szymon Rozga
fonte
0

Uma maneira seria criar um ObservableCollection (System.Collections.ObjectModel) e ter seus dados de dicionário lá. Então, você deve ser capaz de vincular o ObservableCollection ao seu ListBox.

Em seu XAML, você deve ter algo assim:

<ListBox ItemsSource="{Binding Path=Name_of_your_ObservableCollection" />
Parcial
fonte
0

Eu estava tendo exatamente o mesmo problema, mas o meu não era porque estava definindo uma variável local ... Eu estava em uma janela filho e precisava definir um DataContext relativo que acabei de adicionar ao XAML da janela.

<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
fonte
0

Você pode tentar x: truque de referência

<Window ... x:Name="myWindow"><ListBox ItemsSource="{Binding Items, Source={x:Reference myWindow}}" /></Window>
Николай Солдаткин
fonte