Желаемый поток моего проекта:
- Нажатие кнопки на пользовательском интерфейсе запускает запуск таймера.
- Каждый тик таймера изменяет строку, привязанную к тексту TextBox.
- Пользовательский интерфейс обновляет новую строку автоматически.
В течение долгого времени я думал, что у меня возникла проблема с привязкой к данным, потому что пользовательский интерфейс не обновлялся, но потом я обнаружил, вызвал ли я запуск таймера через представлениеМодель, а не внешне через код главного окна, обновил пользовательский интерфейс.Однако это нежелательное поведение, и я пытаюсь понять, почему это так.
Полный просмотр (TextBlock Text="{Binding FileName}"
- это обновляемое свойство):
<Window x:Class="MaterialDesignTest.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"
mc:Ignorable="d"
xmlns:ns="clr-namespace:MaterialDesignTest"
xmlns:local="clr-namespace:MaterialDesignTest"
Title="MainWindow" Height="350" Width="525"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
TextElement.Foreground="{DynamicResource MaterialDesignBody}"
TextElement.FontWeight="Regular"
TextElement.FontSize="13"
TextOptions.TextFormattingMode="Ideal"
TextOptions.TextRenderingMode="Auto"
Background="{DynamicResource MaterialDesignPaper}"
FontFamily="{DynamicResource MaterialDesignFont}">
<materialDesign:DialogHost Identifier="RootDialog" Loaded="DialogHost_Loaded">
<Grid>
<StackPanel>
<Button Width="100" x:Name="SearchRestore" Margin="0 150 0 0" Command="{x:Static materialDesign:DialogHost.OpenDialogCommand}" materialDesign:DialogHost.DialogClosingAttached="SearchRestore_OnDialogClosing"
Content="Restore" Click="SearchRestore_Click">
<Button.CommandParameter>
<StackPanel Margin="10">
<TextBlock Text="Restoring..." HorizontalAlignment="Center" Margin="0 0 0 15"/>
<TextBlock Text="{Binding FileName}" HorizontalAlignment="Center">
<TextBlock.DataContext>
<ns:ProgressViewModel />
</TextBlock.DataContext>
</TextBlock>
<Button Name="CircleButton" Margin="0 50 0 0" Style="{StaticResource MaterialDesignFloatingActionButton}" IsHitTestVisible="False"
materialDesign:ButtonProgressAssist.IsIndicatorVisible="{Binding IsSaving}"
materialDesign:ButtonProgressAssist.Value="{Binding SaveProgressButton}">
<materialDesign:PackIcon Height="24" Width="24" Foreground="White">
<materialDesign:PackIcon.Style>
<Style TargetType="materialDesign:PackIcon" BasedOn="{StaticResource {x:Type materialDesign:PackIcon}}">
<Setter Property="Kind" Value="CloudSync" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsSaveComplete}" Value="True">
<Setter Property="Kind" Value="Check" />
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="0" To="1" Duration="0:0:0.8" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</materialDesign:PackIcon.Style>
</materialDesign:PackIcon>
</Button>
</StackPanel>
</Button.CommandParameter>
</Button>
</StackPanel>
</Grid>
</materialDesign:DialogHost>
Модель полного просмотра (комментирование KickOffProgressTimer();
, приводящее к тому, что пользовательский интерфейс больше не обновляет свойство FileName ):
using MaterialDesignThemes.Wpf;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
namespace MaterialDesignTest
{
public class ProgressViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
System.Windows.Forms.Timer progressTimer;
private string _fileName;
public string FileName
{
get { return _fileName; }
set
{
if (value != _fileName)
{
_fileName = value;
OnPropertyChanged("FileName");
}
}
}
private double _saveProgressButton;
public double SaveProgressButton
{
get { return _saveProgressButton; }
set { this.MutateVerbose(ref _saveProgressButton, value, RaisePropertyChanged()); }
}
private bool _isSaveComplete;
public bool IsSaveComplete
{
get { return _isSaveComplete; }
private set { this.MutateVerbose(ref _isSaveComplete, value, RaisePropertyChanged()); }
}
private bool _isSaving;
public bool IsSaving
{
get { return _isSaving; }
private set { this.MutateVerbose(ref _isSaving, value, RaisePropertyChanged()); }
}
int progress = 0;
int cycles = 0;
public ProgressViewModel()
{
KickOffProgressTimer();
}
public void KickOffProgressTimer()
{
progressTimer = new System.Windows.Forms.Timer();
progressTimer.Tick += new EventHandler(progressTimerTick);
progressTimer.Interval = 140;
progressTimer.Start();
}
private async void progressTimerTick(object sender, EventArgs e)
{
if (progress < 100 && cycles < 2)
{
if (progress == 99)
{
cycles++;
progress = 0;
}
FileName = SelectRandomString();
IsSaveComplete = false;
IsSaving = true;
progress++;
SaveProgressButton = progress;
}
else
{
IsSaveComplete = true;
IsSaving = false;
progressTimer.Stop();
progressTimer.Enabled = false;
SaveProgressButton = 0;
await NonBlockingDelay(1750);
DialogHost.CloseDialogCommand.Execute(null, null);
}
}
async Task NonBlockingDelay(int value)
{
await Task.Delay(value);
}
private Action<PropertyChangedEventArgs> RaisePropertyChanged()
{
return args => PropertyChanged?.Invoke(this, args);
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
static string SelectRandomString()
{
var random = new Random();
var filenames = new List<string>(Directory.GetFiles(@"C:\Windows\System32\"));
int index = random.Next(filenames.Count);
return (filenames[index]);
}
}
}
Логика таймера запуска в коде главного окна:
private void CircleButtonClick(object sender, RoutedEventArgs e)
{
ProgressViewModel pvm = new ProgressViewModel();
CircleButton.DataContext = pvm;
pvm.KickOffProgressTimer();
}