Vinculação à propriedade estática

168

Estou tendo dificuldade para vincular uma propriedade de cadeia estática simples a um TextBox.

Aqui está a classe com a propriedade estática:

public class VersionManager
{
    private static string filterString;

    public static string FilterString
    {
        get { return filterString; }
        set { filterString = value; }
    }
}

No meu xaml, eu só quero vincular essa propriedade estática a um TextBox:

<TextBox>
    <TextBox.Text>
        <Binding Source="{x:Static local:VersionManager.FilterString}"/>
    </TextBox.Text>
</TextBox>

Tudo é compilado, mas em tempo de execução, recebo a seguinte exceção:

Não é possível converter o valor no atributo 'Origem' para o objeto do tipo 'System.Windows.Markup.StaticExtension'. Erro no objeto 'System.Windows.Data.Binding' no arquivo de marcação 'BurnDisk; component / selectversionpagefunction.xaml' Linha 57 Posição 29.

Alguma idéia do que estou fazendo de errado?

Anthony Brien
fonte

Respostas:

168

Se a ligação precisar ser bidirecional, você deverá fornecer um caminho. Há um truque para fazer a ligação bidirecional em uma propriedade estática, desde que a classe não seja estática: declare uma instância fictícia da classe nos recursos e use-a como a fonte da ligação.

<Window.Resources>
    <local:VersionManager x:Key="versionManager"/>
</Window.Resources>
...

<TextBox Text="{Binding Source={StaticResource versionManager}, Path=FilterString}"/>
Thomas Levesque
fonte
Esta resposta é mais apropriada ao meu caso, porque não quero introduzir DependencyObject na minha classe de origem. Obrigado pela dica!
Anthony Brien
6
Observe que permitirá que sua caixa de texto envie o valor de volta à propriedade estática, mas não atualizará a caixa de texto quando o valor de origem for alterado.
Adam Sills
1
Tudo bem, eu só precisava da ligação da caixa de texto para a Fonte neste caso. Se quiser que a ligação funcione de outra maneira, conheço a necessidade de um destes métodos: INotifyPropertyChanged, <PropertyName> Evento alterado ou propriedade de dependência.
Anthony Brien
1
Nota: Esta solução não funcionará em uma situação MVVM, pois geralmente você não tem acesso aos tipos de objetos aos quais está vinculando.
Antony Woods
@ Thomas Eu adoraria fazer isso funcionar para mim, mas não posso. Eu postei o meu dilema como outra pergunta aqui: stackoverflow.com/questions/34656670/...
Andrew Simpson
107

Você não pode se ligar a uma estática assim. Não há como a infraestrutura de ligação ser notificada de atualizações, pois não há DependencyObject(ou instância de objeto que implemente INotifyPropertyChanged) envolvida.

Se esse valor não mudar, apenas abandone a ligação e use x:Staticdiretamente dentro da Textpropriedade. Defina appabaixo como o local do espaço para nome (e montagem) da classe VersionManager.

<TextBox Text="{x:Static app:VersionManager.FilterString}" />

Se o valor mudar, sugiro criar um singleton para conter o valor e vincular a ele.

Um exemplo do singleton:

public class VersionManager : DependencyObject {
    public static readonly DependencyProperty FilterStringProperty =
        DependencyProperty.Register( "FilterString", typeof( string ),
        typeof( VersionManager ), new UIPropertyMetadata( "no version!" ) );
    public string FilterString {
        get { return (string) GetValue( FilterStringProperty ); }
        set { SetValue( FilterStringProperty, value ); }
    }

    public static VersionManager Instance { get; private set; }

    static VersionManager() {
        Instance = new VersionManager();
    }
}
<TextBox Text="{Binding Source={x:Static local:VersionManager.Instance},
                        Path=FilterString}"/>
Adam Sills
fonte
5
Realmente? Consegui vincular o Int32.MaxValue estático, que é muito semelhante ao meu exemplo: <TextBox Text = {Binding Source = {x: Static sys: Int32.MaxValue}, Mode = OneWay} "/> trabalhar porque é um caminho?
Anthony Brien
2
Sim, qualquer ligação bidirecional requer um valor de propriedade Path na ligação. A origem precisa ser um objeto que contenha a propriedade especificada por Path. A especificação do OneWay remove essa restrição.
236 Adam Sills
Sinto muito pela atualização tardia, mas atualizei a resposta acima com uma amostra.
Adam Sills
Existe uma maneira de vincular uma sequência estática. Eu tenho uma ligação múltipla e uma das entradas é uma string fixa.
Nitin Chaudhari
39

No .NET 4.5, é possível vincular a propriedades estáticas, leia mais

Você pode usar propriedades estáticas como a fonte de uma ligação de dados. O mecanismo de ligação de dados reconhece quando o valor da propriedade é alterado se um evento estático for gerado. Por exemplo, se a classe SomeClass definir uma propriedade estática chamada MyProperty, SomeClass poderá definir um evento estático que será gerado quando o valor de MyProperty for alterado. O evento estático pode usar uma das seguintes assinaturas:

public static event EventHandler MyPropertyChanged; 
public static event EventHandler<PropertyChangedEventArgs> StaticPropertyChanged; 

Observe que, no primeiro caso, a classe expõe um evento estático chamado PropertyNameChanged que passa EventArgs para o manipulador de eventos. No segundo caso, a classe expõe um evento estático chamado StaticPropertyChanged que passa PropertyChangedEventArgs ao manipulador de eventos. Uma classe que implementa a propriedade estática pode optar por gerar notificações de alteração de propriedade usando um dos métodos.

Jowen
fonte
Aqui está o link, caso alguém queira ler mais. A Microsoft retirou o arquivo, mas está no arquivo da web aqui. web.archive.org/web/20131129053934/http://msdn.microsoft.com/…
C. Tewalt
Essa resposta me apontou na direção certa, mas ainda demorou um pouco para descobrir os detalhes sem um exemplo. Eu escrevi um exemplo com base no código original.
Matt
13

No WPF 4.5, é possível vincular diretamente às propriedades estáticas e atualizar automaticamente quando a propriedade é alterada. Você precisa conectar manualmente um evento de alteração para acionar as atualizações de ligação.

public class VersionManager
{
    private static String _filterString;        

    /// <summary>
    /// A static property which you'd like to bind to
    /// </summary>
    public static String FilterString
    {
        get
        {
            return _filterString;
        }

        set
        {
            _filterString = value;

            // Raise a change event
            OnFilterStringChanged(EventArgs.Empty);
        }
    }

    // Declare a static event representing changes to your static property
    public static event EventHandler FilterStringChanged;

    // Raise the change event through this static method
    protected static void OnFilterStringChanged(EventArgs e)
    {
        EventHandler handler = FilterStringChanged;

        if (handler != null)
        {
            handler(null, e);
        }
    }

    static VersionManager()
    {
        // Set up an empty event handler
        FilterStringChanged += (sender, e) => { return; };
    }

}

Agora você pode vincular sua propriedade estática como qualquer outra:

<TextBox Text="{Binding Path=(local:VersionManager.FilterString)}"/>
Matt
fonte
1
A VersionManagerclasse pode ser estática e tudo ainda funciona. Observe as chaves na definição de caminho Path=(local:VersionManager.FilterString). Alguém sabe por que eles são realmente necessários?
chviLadislav 26/09/19
2
Os chavetas na definição do caminho são necessários porque a propriedade é estática, veja aqui
chviLadislav
11

Pode haver duas maneiras / sintaxe para vincular uma staticpropriedade. Se p for uma staticpropriedade na classe MainWindow, bindingfor textboxserá:

1

<TextBox Text="{x:Static local:MainWindow.p}" />

2)

<TextBox Text="{Binding Source={x:Static local:MainWindow.p},Mode=OneTime}" />
Kylo Ren
fonte
9

Você pode usar ObjectDataProviderclasse e sua MethodNamepropriedade. Pode ficar assim:

<Window.Resources>
   <ObjectDataProvider x:Key="versionManager" ObjectType="{x:Type VersionManager}" MethodName="get_FilterString"></ObjectDataProvider>
</Window.Resources>

O provedor de dados do objeto declarado pode ser usado assim:

<TextBox Text="{Binding Source={StaticResource versionManager}}" />
GPAshka
fonte
8

Se você estiver usando recursos locais, pode consultá-los como abaixo:

<TextBlock Text="{Binding Source={x:Static prop:Resources.PerUnitOfMeasure}}" TextWrapping="Wrap" TextAlignment="Center"/>
Edmund Covington
fonte
3

Variante correta para .NET 4.5 +

Código c #

public class VersionManager
{
    private static string filterString;

    public static string FilterString
    {
        get => filterString;
        set
        {
            if (filterString == value)
                return;

            filterString = value;

            StaticPropertyChanged?.Invoke(null, FilterStringPropertyEventArgs);
        }
    }

    private static readonly PropertyChangedEventArgs FilterStringPropertyEventArgs = new PropertyChangedEventArgs (nameof(FilterString));
    public static event PropertyChangedEventHandler StaticPropertyChanged;
}

Ligação XAML (atenção aos chavetas são (), não {})

<TextBox Text="{Binding Path=(yournamespace:VersionManager.FilterString)}" />
Alexei Shcherbakov
fonte
Fez uma pequena alteração no seu código para chamar corretamente o EventHandler.
Mark A. Donohoe 8/18
Tentei muitas soluções diferentes e esta funcionou. O PropertyChangedEventHandler é o que funcionou para mim. Felicidades.
Mgamerz 24/09/19
2

Veja o meu projeto CalcBinding , que fornece a você expressões complexas no valor da propriedade Path, incluindo propriedades estáticas, propriedades de origem, Math e outras. Então, você pode escrever isso:

<TextBox Text="{c:Binding local:VersionManager.FilterString}"/>

Boa sorte!

Alex141
fonte
0

Essas respostas são boas se você deseja seguir boas convenções, mas o OP queria algo simples , que é o que eu também queria, em vez de lidar com os padrões de design da GUI. Se tudo o que você quer é ter uma string em um aplicativo GUI básico, você pode atualizar ad-hoc sem nada sofisticado, basta acessá-lo diretamente na sua fonte C #.

Digamos que você tenha um aplicativo WPF realmente básico, o MainWindow XAML assim,

<Window x:Class="MyWPFApp.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:MyWPFApp"
            mc:Ignorable="d"
            Title="MainWindow"
            Height="200"
            Width="400"
            Background="White" >
    <Grid>
        <TextBlock x:Name="textBlock"                   
                       Text=".."
                       HorizontalAlignment="Center"
                       VerticalAlignment="Top"
                       FontWeight="Bold"
                       FontFamily="Helvetica"
                       FontSize="16"
                       Foreground="Blue" Margin="0,10,0,0"
             />
        <Button x:Name="Find_Kilroy"
                    Content="Poke Kilroy"
                    Click="Button_Click_Poke_Kilroy"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Center"
                    FontFamily="Helvetica"
                    FontWeight="Bold"
                    FontSize="14"
                    Width="280"
            />
    </Grid>
</Window>

Isso será algo como isto:

insira a descrição da imagem aqui

Na fonte do MainWindow XAML, você pode ter algo parecido com isto, onde tudo o que estamos fazendo é alterar o valor diretamente por meio textBlock.Textda funcionalidade get/ set:

using System.Windows;

namespace MyWPFApp
{
    public partial class MainWindow : Window
    {
        public MainWindow() { InitializeComponent(); }

        private void Button_Click_Poke_Kilroy(object sender, RoutedEventArgs e)
        {
            textBlock.Text = "              \\|||/\r\n" +
                             "              (o o) \r\n" +
                             "----ooO- (_) -Ooo----";
        }
    }
}

Então, quando você acionar o evento de clique clicando no botão, pronto! Kilroy aparece :)

insira a descrição da imagem aqui

kayleeFrye_onDeck
fonte
0

Outra solução é criar uma classe normal que implemente o PropertyChanger assim

public class ViewProps : PropertyChanger
{
    private string _MyValue = string.Empty;
    public string MyValue
    {
        get { 
            return _MyValue
        }
        set
        {
            if (_MyValue == value)
            {
                return;
            }
            SetProperty(ref _MyValue, value);
        }
    }
}

Em seguida, crie uma instância estática da classe em algum lugar que você não

public class MyClass
{
    private static ViewProps _ViewProps = null;
    public static ViewProps ViewProps
    {
        get
        {
            if (_ViewProps == null)
            {
                _ViewProps = new ViewProps();
            }
            return _ViewProps;
        }
    }
}

E agora use-o como propriedade estática

<TextBlock  Text="{x:Bind local:MyClass.ViewProps.MyValue, Mode=OneWay}"  />

E aqui está a implementação do PropertyChanger, se necessário

public abstract class PropertyChanger : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
    {
        if (object.Equals(storage, value)) return false;

        storage = value;
        OnPropertyChanged(propertyName);
        return true;
    }

    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
neosonne
fonte
-1

Resposta mais simples (.net 4.5 e posterior):

    static public event EventHandler FilterStringChanged;
    static string _filterString;
    static public string FilterString
    {
        get { return _filterString; }
        set
        {
            _filterString= value;
            FilterStringChanged?.Invoke(null, EventArgs.Empty);
        }
    }

e XAML:

    <TextBox Text="{Binding Path=(local:VersionManager.FilterString)}"/>

Não negligencie os colchetes

Sean
fonte