Основная проблема в вашем коде заключается в том, что System.Windows.Media.SolidColorBrush
не переопределяет метод Equals()
, и поэтому ваше выражение Background == Brushes.Black
никогда не будет true
.Поскольку вы создаете явные новые экземпляры объекта SolidColorBrush
, а оператор ==
просто сравнивает ссылки на экземпляры, сравнение между значением кисти и встроенным экземпляром Brushes.Black
всегда завершается неудачей.
Самый простой способ исправить код - это просто использовать фактические Brushes
экземпляры:
private void Timer_Tick(object sender, System.EventArgs e)
{
if (Background == Brushes.Black)
{
Background = Brushes.White;
Title = "White";
}
else
{
Background = Brushes.Black;
Title = "Black";
}
}
Затем, когда вы сравниваете ссылки на экземпляры, они на самом деле сопоставимы, и вы обнаружите "черное "условие по желанию.
Я отмечу, что, поскольку вы также не повышаете PropertyChanged
для изменений свойства Title
, эта привязка также не будет работать должным образом.
Для чего бы это ни стоило, я бы вообще избежал вашего дизайна.Во-первых, при просмотре объектов модели следует избегать использования специфичных для пользовательского интерфейса типов.Конечно, это будет включать тип Brush
.Возможно, он также включает в себя DispatcherTimer
, так как он существует в обслуживании пользовательского интерфейса.Помимо этого, DispatcherTimer
является относительно неточным таймером, и хотя он существует, главным образом, для того, чтобы иметь таймер, который вызывает его событие Tick
в потоке диспетчера, которому принадлежит таймер, поскольку WPF автоматически маршализирует события изменения свойств из любого другого потока впоток пользовательского интерфейса, он не так полезен в этом примере.
Вот версия вашей программы, которая IMHO больше соответствует типичным практикам программирования WPF:
class MainViewModel : NotifyPropertyChangedBase
{
private string _title;
public string Title
{
get { return _title; }
set { _UpdateField(ref _title, value); }
}
private bool _isBlack;
public bool IsBlack
{
get { return _isBlack; }
set { _UpdateField(ref _isBlack, value, _OnIsBlackChanged); }
}
private void _OnIsBlackChanged(bool obj)
{
Title = IsBlack ? "Black" : "White";
}
public MainViewModel()
{
IsBlack = true;
_ToggleIsBlack(); // fire and forget
}
private async void _ToggleIsBlack()
{
while (true)
{
await Task.Delay(TimeSpan.FromMilliseconds(100));
IsBlack = !IsBlack;
}
}
}
Эта модель представлениякласс использует базовый класс, который я использую для всех своих моделей представлений, поэтому мне не нужно постоянно переопределять INotifyPropertyChanged
:
class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void _UpdateField<T>(ref T field, T newValue,
Action<T> onChangedCallback = null,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return;
}
T oldValue = field;
field = newValue;
onChangedCallback?.Invoke(oldValue);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Вы заметите, что класс модели представлений неиметь любое поведение, специфичное для пользовательского интерфейса.Он будет работать с любой программой, WPF или другой, если у этой программы есть способ реагировать на PropertyChanged
события и использовать значения в модели представления.
Чтобы это работало,XAML становится несколько более многословным:
<Window x:Class="TestSO55437213TimerBackgroundColor.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:p="http://schemas.microsoft.com/netfx/2007/xaml/presentation"
xmlns:l="clr-namespace:TestSO55437213TimerBackgroundColor"
mc:Ignorable="d"
Title="{Binding Title}" Height="450" Width="800">
<Window.DataContext>
<l:MainViewModel/>
</Window.DataContext>
<Grid>
<Grid.Style>
<p:Style TargetType="Grid">
<Setter Property="Background" Value="White"/>
<p:Style.Triggers>
<DataTrigger Binding="{Binding IsBlack}" Value="True">
<Setter Property="Background" Value="Black"/>
</DataTrigger>
</p:Style.Triggers>
</p:Style>
</Grid.Style>
</Grid>
</Window>
(Примечание. Я явно назвал пространство имен http://schemas.microsoft.com/netfx/2007/xaml/presentation XML для использования с элементом <Style/>
, исключительно как обходной путь для стекаНедостаточная обработка XML-разметки в Overflow, которая иначе не распознала бы элемент <Style/>
в качестве фактического элемента XML. В вашей собственной программе вы можете не обращать на это внимания.)
Ключом здесь является то, что весьобработка озабоченности UI находится в самой декларации UI.Модель представления не должна знать, как пользовательский интерфейс представляет черный или белый цвета.Это просто переключает флаг.Затем пользовательский интерфейс отслеживает этот флаг и применяет установщики свойств в соответствии с его текущим значением.
Наконец, я отмечу, что для многократного изменения состояния в пользовательском интерфейсе, подобного этому, другой подход заключается в использовании функций анимации WPF.,Это выходит за рамки этого ответа, но я призываю вас прочитать об этом.Одним из преимуществ этого является то, что для анимации используется модель синхронизации даже с более высоким разрешением, чем метод Task.Delay()
, основанный на пуле потоков, который я использовал выше, и, как правило, она обеспечивает еще более плавную анимацию (хотя, конечно, поскольку ваш интервал становится меньше именьше - например, 25 мс, поскольку ваше сообщение указывает на то, что вы намеревались использовать - у вас будут проблемы с тем, чтобы WPF не отставал, независимо от того, в какой-то момент вы обнаружите, что высокоуровневые инфраструктуры пользовательского интерфейса, такие как WinForms, WPF, Xamarin и т. д.просто не могу работать на таком мелкозернистом уровне таймера).