Powiązanie z właściwością statyczną

168

Trudno mi jest powiązać prostą statyczną właściwość ciągu z TextBox.

Oto klasa z właściwością statyczną:

public class VersionManager
{
    private static string filterString;

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

W moim XAML chcę po prostu powiązać tę statyczną właściwość z TextBox:

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

Wszystko się kompiluje, ale w czasie wykonywania pojawia się następujący wyjątek:

Nie można przekonwertować wartości atrybutu „Source” na obiekt typu „System.Windows.Markup.StaticExtension”. Błąd w obiekcie „System.Windows.Data.Binding” w pliku znaczników „BurnDisk; component / selectversionpagefunction.xaml” Wiersz 57 Pozycja 29.

Masz pojęcie, co robię źle?

Anthony Brien
źródło

Odpowiedzi:

168

Jeśli wiązanie musi być dwukierunkowe, musisz podać ścieżkę. Istnieje sztuczka polegająca na wykonaniu dwukierunkowego wiązania na właściwości statycznej, pod warunkiem, że klasa nie jest statyczna: zadeklaruj fałszywe wystąpienie klasy w zasobach i użyj go jako źródła powiązania.

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

<TextBox Text="{Binding Source={StaticResource versionManager}, Path=FilterString}"/>
Thomas Levesque
źródło
Ta odpowiedź jest bardziej odpowiednia w moim przypadku, ponieważ nie chcę wprowadzać DependencyObject do mojej klasy źródłowej. Dzięki za wskazówkę!
Anthony Brien
6
Zauważ, że umożliwi to polu tekstowemu wypchnięcie wartości z powrotem do właściwości statycznej, ale nie zaktualizuje pola tekstowego, gdy zmieni się wartość źródłowa.
Adam Sills
1
W porządku, w tym przypadku potrzebowałem tylko powiązania z pola tekstowego do źródła. Jeśli chcę, aby powiązanie działało w inny sposób, zdaję sobie sprawę z potrzeby jednej z następujących metod: INotifyPropertyChanged, <PropertyName> Changed zdarzenie lub właściwość zależności.
Anthony Brien
1
Uwaga: to rozwiązanie nie zadziała w sytuacji MVVM, ponieważ zazwyczaj nie masz dostępu do typów obiektów, z którymi się łączysz.
Antony Woods
@thomas Chciałbym, żeby to zadziałało, ale nie mogę. Przedstawiłem mój dylemat jako kolejne pytanie tutaj: stackoverflow.com/questions/34656670/ ...
Andrew Simpson
107

Nie możesz przywiązywać się do takiego statycznego. Nie ma możliwości, aby infrastruktura powiązania otrzymywała powiadomienia o aktualizacjach, ponieważ nie ma DependencyObject(lub wystąpienia obiektu, które implementuje INotifyPropertyChanged).

Jeśli ta wartość się nie zmieni, po prostu porzuć powiązanie i użyj x:Staticbezpośrednio wewnątrz Textwłaściwości. Zdefiniuj appponiżej jako lokalizację przestrzeni nazw (i zestawu) klasy VersionManager.

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

Jeśli wartość się zmieni, sugerowałbym utworzenie singletona zawierającego wartość i powiązanego z nią.

Przykład singletona:

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
źródło
5
Naprawdę? Udało mi się powiązać ze statyczną wartością Int32.MaxValue, która jest bardzo podobna do mojej próbki: <TextBox Text = {Binding Source = {x: Static sys: Int32.MaxValue}, Mode = OneWay} "/> Czy to działa, ponieważ jest w jedną stronę?
Anthony Brien
2
Tak, każde powiązanie dwukierunkowe wymaga wartości właściwości Path w powiązaniu. Źródło musi być obiektem zawierającym właściwość określoną przez Path. Określenie OneWay usuwa to ograniczenie.
Adam Sills
Przepraszam za późną aktualizację, ale zaktualizowałem powyższą odpowiedź próbką.
Adam Sills
Czy istnieje sposób na powiązanie statycznego łańcucha? Mam mutibinding, a jednym z danych wejściowych jest ustalony ciąg.
Nitin Chaudhari,
39

W .NET 4.5 można powiązać właściwości statyczne, przeczytaj więcej

Możesz użyć właściwości statycznych jako źródła powiązania danych. Silnik powiązań danych rozpoznaje, kiedy wartość właściwości zmienia się, jeśli zostanie zgłoszone zdarzenie statyczne. Na przykład, jeśli klasa SomeClass definiuje właściwość statyczną o nazwie MyProperty, SomeClass może zdefiniować zdarzenie statyczne, które jest zgłaszane, gdy zmienia się wartość MyProperty. Zdarzenie statyczne może używać jednego z następujących podpisów:

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

Należy zauważyć, że w pierwszym przypadku klasa ujawnia zdarzenie statyczne o nazwie PropertyNameChanged, które przekazuje EventArgs do programu obsługi zdarzeń. W drugim przypadku klasa ujawnia zdarzenie statyczne o nazwie StaticPropertyChanged, które przekazuje PropertyChangedEventArgs do procedury obsługi zdarzeń. Klasa, która implementuje właściwość statyczną, może wybrać zgłaszanie powiadomień o zmianie właściwości przy użyciu dowolnej metody.

Jowen
źródło
Oto link na wypadek, gdyby ktoś chciał przeczytać więcej. Microsoft usunął go, ale jest w archiwum internetowym tutaj. web.archive.org/web/20131129053934/http://msdn.microsoft.com/…
C. Tewalt
Ta odpowiedź wskazała mi właściwy kierunek, ale dopracowanie szczegółów bez przykładu zajęło mi trochę czasu. Napisałem przykład na podstawie oryginalnego kodu.
Matt,
13

Począwszy od WPF 4,5 można powiązać bezpośrednio z właściwościami statycznymi, a powiązanie jest automatycznie aktualizowane po zmianie właściwości. Musisz ręcznie połączyć zdarzenie zmiany, aby wyzwolić aktualizacje powiązań.

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

}

Możesz teraz powiązać swoją statyczną właściwość tak jak każdą inną:

<TextBox Text="{Binding Path=(local:VersionManager.FilterString)}"/>
Matt
źródło
1
VersionManagerKlasa może być statyczna i wszystko nadal działa. Zwróć uwagę na nawiasy klamrowe w definicji ścieżki Path=(local:VersionManager.FilterString). Czy ktoś wie, dlaczego są faktycznie potrzebne?
chviLadislav
2
Nawiasy klamrowe w definicji ścieżki są potrzebne, ponieważ właściwość jest statyczna, patrz tutaj
chviLadislav
11

Mogą istnieć dwa sposoby / składnia na powiązanie staticwłaściwości. Jeśli p jest staticwłasnością w klasie MainWindow, to bindingfor textboxbędzie:

1.

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

2.

<TextBox Text="{Binding Source={x:Static local:MainWindow.p},Mode=OneTime}" />
Kylo Ren
źródło
9

Możesz użyć ObjectDataProviderklasy i jej MethodNamewłaściwości. Może to wyglądać tak:

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

Zadeklarowany dostawca danych obiektowych może być używany w następujący sposób:

<TextBox Text="{Binding Source={StaticResource versionManager}}" />
GPAshka
źródło
8

Jeśli korzystasz z zasobów lokalnych, możesz się do nich odwołać, jak poniżej:

<TextBlock Text="{Binding Source={x:Static prop:Resources.PerUnitOfMeasure}}" TextWrapping="Wrap" TextAlignment="Center"/>
Edmund Covington
źródło
3

Odpowiedni wariant dla .NET 4.5 +

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

Powiązanie XAML (uwaga na nawiasy klamrowe, którymi są (), a nie {})

<TextBox Text="{Binding Path=(yournamespace:VersionManager.FilterString)}" />
Alexei Shcherbakov
źródło
Dokonałem niewielkiej zmiany w kodzie, aby poprawnie wywołać EventHandler.
Mark A. Donohoe
Wypróbowałem wiele różnych rozwiązań i to zadziałało. PropertyChangedEventHandler jest tym, co zadziałało dla mnie. Twoje zdrowie.
Mgamerz
2

Spójrz na mój projekt CalcBinding , który umożliwia pisanie złożonych wyrażeń w wartości właściwości Path, w tym właściwości statycznych, właściwości źródłowych, Math i innych. Możesz więc napisać to:

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

Powodzenia!

Alex141
źródło
0

Wszystkie te odpowiedzi są dobre, jeśli chcesz przestrzegać dobrych konwencji, ale OP chciał czegoś prostego , czego też chciałem, zamiast zajmować się wzorcami projektowymi GUI. Jeśli wszystko, co chcesz zrobić, to mieć ciąg w podstawowej aplikacji GUI, możesz aktualizować ad-hoc bez niczego szczególnego, możesz po prostu uzyskać do niego dostęp bezpośrednio w źródle C #.

Załóżmy, że masz naprawdę podstawową aplikację WPF MainWindow XAML, taką jak ta,

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

To będzie wyglądać mniej więcej tak:

wprowadź opis obrazu tutaj

W źródle MainWindow XAML jest, można mieć coś takiego, gdzie wszystko robimy na zmianę wartości bezpośrednio poprzez textBlock.Text„s get/ setfunkcjonalność:

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

Następnie, gdy uruchomisz zdarzenie kliknięcia, klikając przycisk, voila! Pojawia się Kilroy :)

wprowadź opis obrazu tutaj

kayleeFrye_onDeck
źródło
0

Innym rozwiązaniem jest utworzenie normalnej klasy, która implementuje PropertyChanger w ten sposób

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

Następnie utwórz statyczną instancję klasy gdzieś, gdzie nie chcesz

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

A teraz użyj go jako właściwości statycznej

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

A oto implementacja PropertyChanger, jeśli to konieczne

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
źródło
-1

Najmniejsza odpowiedź (.net 4.5 i nowsze):

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

i XAML:

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

Nie zaniedbuj nawiasów

Sean
źródło