Привязка к статическому свойству

168

Мне трудно связать простое статическое свойство строки с TextBox.

Вот класс со статическим свойством:

public class VersionManager
{
    private static string filterString;

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

В моем xaml я просто хочу связать это статическое свойство с TextBox:

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

Все компилируется, но во время выполнения я получаю следующее исключение:

Невозможно преобразовать значение в атрибуте «Источник» в объект типа «System.Windows.Markup.StaticExtension». Ошибка в объекте «System.Windows.Data.Binding» в файле разметки «BurnDisk; component / selectversionpagefunction.xaml» Строка 57 Позиция 29.

Есть идеи, что я делаю не так?

Энтони Брайен
источник

Ответы:

169

Если привязка должна быть двусторонней, необходимо указать путь.

Существует хитрость для двусторонней привязки статического свойства, при условии, что класс не является статическим: объявите фиктивный экземпляр класса в ресурсах и используйте его в качестве источника привязки.

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

<TextBox Text="{Binding Source={StaticResource versionManager}, Path=FilterString}"/>
Томас Левеск
источник
Этот ответ больше подходит для моего случая, потому что я не хочу вводить DependencyObject в мой исходный класс. Спасибо за чаевые!
Энтони Брайен
6
Обратите внимание, что это позволит вашему текстовому полю возвращать значение обратно в статическое свойство, но не будет обновлять текстовое поле при изменении исходного значения.
Адам Силлс
1
Это нормально, мне просто нужно было связать текстовое поле с источником в этом случае. Если я хочу, чтобы привязка работала другим способом, я знаю о необходимости одного из следующих методов: INotifyPropertyChanged, <PropertyName> Измененное событие или свойство зависимости.
Энтони Брайен
1
Примечание. Это решение не будет работать в ситуации MVVM, поскольку у вас обычно нет доступа к типам объектов, к которым вы привязываете.
Энтони Вудс,
@ Я бы хотел, чтобы это сработало для меня, но не могу. Я отправил свою дилемму как еще один вопрос здесь: stackoverflow.com/questions/34656670/…
Эндрю Симпсон
107

Вы не можете привязать к такой статике. Инфраструктура привязки не может получать уведомления об обновлениях, так как она не задействована DependencyObject(или экземпляр объекта, который реализует INotifyPropertyChanged).

Если это значение не меняется, просто отмените привязку и используйте x:Staticнепосредственно внутри Textсвойства. Определите appниже, чтобы быть местоположением пространства имен (и сборки) класса VersionManager.

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

Если значение действительно меняется, я бы предложил создать синглтон, содержащий значение и привязанный к нему.

Пример синглтона:

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}"/>
Адам Силлс
источник
5
В самом деле? Я смог сделать привязку к статическому Int32.MaxValue, который очень похож на мой пример: <TextBox Text = {Binding Source = {x: Static sys: Int32.MaxValue}, Mode = OneWay} "/> Это так? работать, потому что это один из способов?
Энтони Брайен
2
Да, любая двусторонняя привязка требует значения свойства Path для привязки. Источник должен быть объектом, который содержит свойство, указанное в Path. Указание OneWay снимает это ограничение.
Адам Силлс
Также, извините за позднее обновление, но я обновил ответ выше с образцом.
Адам Силлс
Есть ли способ связать статическую строку. У меня есть mutibinding, и один из входных данных является фиксированной строкой.
Нитин Чаудхари
39

В .NET 4.5 можно привязывать к статическим свойствам, подробнее

Вы можете использовать статические свойства в качестве источника привязки данных. Механизм привязки данных распознает, когда значение свойства изменяется, если возникает статическое событие. Например, если класс SomeClass определяет статическое свойство с именем MyProperty, SomeClass может определить статическое событие, которое вызывается при изменении значения MyProperty. Статическое событие может использовать одну из следующих подписей:

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

Обратите внимание, что в первом случае класс предоставляет статическое событие с именем PropertyNameChanged, которое передает EventArgs в обработчик событий. Во втором случае класс предоставляет статическое событие с именем StaticPropertyChanged, которое передает PropertyChangedEventArgs в обработчик событий. Класс, который реализует статическое свойство, может создавать уведомления об изменении свойства с использованием любого метода.

Jowen
источник
Вот ссылка на тот случай, если кто-то захочет узнать больше. Microsoft сняла его, но он есть в веб-архиве здесь. web.archive.org/web/20131129053934/http://msdn.microsoft.com/…
C. Tewalt,
Этот ответ указал мне верное направление, но все еще требовалось время, чтобы проработать детали без примера. Я написал пример, основанный на оригинальном коде.
Мэтт
13

Начиная с WPF 4.5, вы можете связывать напрямую со статическими свойствами и автоматически обновлять привязку при изменении вашего свойства. Вам необходимо вручную подключить событие изменения, чтобы вызвать обновления привязки.

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

}

Теперь вы можете связать свое статическое свойство, как и любое другое:

<TextBox Text="{Binding Path=(local:VersionManager.FilterString)}"/>
Matt
источник
1
VersionManagerКласс может быть статическим , и все еще работает. Обратите внимание на фигурные скобки в определении пути Path=(local:VersionManager.FilterString). Кто-нибудь знает, зачем они на самом деле нужны?
ЧвиЛадислав
2
Скобки в определении пути необходимы, потому что свойство статично, см. Здесь
chviLadislav
11

Может быть два способа / синтаксиса для привязки staticсвойства. Если р является staticсвойство в классе MainWindow, то bindingдля textboxбудет:

1.

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

2.

<TextBox Text="{Binding Source={x:Static local:MainWindow.p},Mode=OneTime}" />
Кило Рен
источник
9

Вы можете использовать ObjectDataProviderкласс и его MethodNameсвойство. Это может выглядеть так:

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

Заявленный объект данных провайдера можно использовать так:

<TextBox Text="{Binding Source={StaticResource versionManager}}" />
GPAshka
источник
8

Если вы используете локальные ресурсы, вы можете ссылаться на них, как показано ниже:

<TextBlock Text="{Binding Source={x:Static prop:Resources.PerUnitOfMeasure}}" TextWrapping="Wrap" TextAlignment="Center"/>
Эдмунд Ковингтон
источник
3

Правильный вариант для .NET 4.5 +

Код 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;
}

Привязка к XAML (внимание к скобкам: (), а не {})

<TextBox Text="{Binding Path=(yournamespace:VersionManager.FilterString)}" />
Алексей Щербаков
источник
Сделано небольшое изменение в вашем коде для правильного вызова EventHandler.
Марк А. Донохо,
Перепробовал много разных решений, и это сработало. PropertyChangedEventHandler - это то, что сработало для меня. Приветствия.
Mgamerz
2

Посмотрите на мой проект CalcBinding , который предоставляет вам написание сложных выражений в значении свойства Path, включая статические свойства, свойства источника, Math и другие. Итак, вы можете написать это:

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

Удачи!

Alex141
источник
0

Все эти ответы хороши, если вы хотите следовать хорошим соглашениям, но OP хотел чего-то простого , чего я и хотел, вместо того, чтобы иметь дело с шаблонами проектирования GUI. Если все, что вам нужно сделать, это добавить строку в базовое приложение с графическим интерфейсом, вы можете обновить ad-hoc без каких-либо сложностей, вы можете просто получить к ней доступ прямо в исходном коде C #.

Допустим, у вас есть действительно простое приложение WPF MainWindow XAML, как это,

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

Это будет выглядеть примерно так:

введите описание изображения здесь

В исходном коде вашего MainWindow XAML у вас может быть что-то вроде этого, где все, что мы делаем, изменяя значение напрямую через textBlock.Texts get/ setfunctions:

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

Затем, когда вы запускаете это событие, нажав кнопку, вуаля! Килрой появляется :)

введите описание изображения здесь

kayleeFrye_onDeck
источник
0

Другое решение заключается в создании нормального класса, который реализует PropertyChanger следующим образом

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

Затем создайте статический экземпляр класса, где вы не будете

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

А теперь используйте его как статическое свойство

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

А вот реализация PropertyChanger, если это необходимо

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
источник
-1

Самый простой ответ (.net 4.5 и выше):

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

и XAML:

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

Не пренебрегайте скобками

Шон
источник