In my application I have a view that's being opened in the following way:
ManagerView view = new ManagerView();
view.ShowDialog();
This is the View:
<Window x:Class="WpfUpdateGui.ManagerView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:WpfUpdateGui">
<Window.DataContext>
<local:ManagerViewModel />
</Window.DataContext>
<i:Interaction.Triggers>
<i:EventTrigger EventName="ContentRendered">
<i:InvokeCommandAction Command="{Binding LoadedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<TextBox Text="{Binding Messages}" />
and my ViewModel:
public class ManagerViewModel : INotifyPropertyChanged
{
/*INPC Members...*/
private string _messages;
private static EventWaitHandle _timerWaiter;
/*Constructor*/
public ManagerViewModel()
{
_timerWaiter = new EventWaitHandle(false, EventResetMode.AutoReset);
LoadedCommand = new RelayCommand(StartProcess);
}
private void StartProcess()
{
Application.Current.Dispatcher.Invoke(
DispatcherPriority.ApplicationIdle,
new Action(() =>
{
AddMessage("Starting");
Worker worker = new Worker();
worker.DidSomethingEvent += Worker_DidSomethingEvent;
worker.DoSomeThing();
_timerWaiter.WaitOne();
AddMessage("Finished");
}));
}
private void AddMessage(string message)
{
Application.Current.Dispatcher.Invoke(() => Messages += $"\r\n{message}");
}
private void Worker_DidSomethingEvent()
{
_timerWaiter.Set();
}
public RelayCommand LoadedCommand { get; set; }
public string Messages
{
get { return _messages; }
set
{
if (value == Messages) return;
_messages = value;
OnPropertyChanged("Messages");
}
}
}
public class Worker
{
public event Action DidSomethingEvent;
public void DoSomeThing()
{
Thread.Sleep(2500);
DidSomethingEvent();
}
}
My problem is that the first message I want to display ("Starting") is displayed only after the EventWaitHandle was set, even tough it was added before the WaitOne() call.
Just replace ContentRendered with Loaded event and it should work. ( It did work with me).
Related
The approch of my little WPF App is first to copy x files and the user shall see this on the progressbar.
I only find ways to do it with a button which command is binded to the VM.
Isn't there any possible way to call this all on startup without the use of a button?
Edit:
This is an example how I tried:
public ViewModelDfsync()
{
this.instigateWorkCommand =
new RelayCommand(o => this.worker.RunWorkerAsync(),
o => !this.worker.IsBusy);
this.worker = new BackgroundWorker();
this.worker.DoWork += this.DoWork;
this.worker.ProgressChanged += this.ProgressChanged;
}
private readonly BackgroundWorker worker;
private readonly RelayCommand instigateWorkCommand;
// your UI binds to this command in order to kick off the work
public RelayCommand InstigateWorkCommand
{
get { return this.instigateWorkCommand; }
}
private void DoWork(object sender, DoWorkEventArgs e)
{
// do time-consuming work here, calling ReportProgress as and when you can
var files = Directory.GetFiles(#"C:\files");
var destDir = #"C:\files\newDir";
foreach (var file in files)
{
FileInfo fileName = new FileInfo(file);
FileInfo destFile = new FileInfo(Path.Combine(destDir, fileName.Name));
if (destFile.Exists)
{
if (fileName.LastWriteTime > destFile.LastWriteTime)
{
fileName.CopyTo(destFile.FullName, true);
worker.ReportProgress(CurrentProgress);
}
}
else
{
fileName.CopyTo(destFile.FullName);
worker.ReportProgress(CurrentProgress);
}
}
}
private void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.CurrentProgress = e.ProgressPercentage;
}
private int currentProgress;
public int CurrentProgress
{
get => currentProgress;
private set
{
if (currentProgress != value)
{
currentProgress = value;
RaisePropertyChanged("CurrentProgress");
}
}
}
this is my progressbar in xaml:
<ProgressBar Minimum="0" Maximum="100" Value="{Binding CurrentProgress, Mode=OneWay}" Height="20"/>
Call a command of the view model once the view/window has been loaded, either programmatically:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var vm = DataContext as ViewModel;
vm.YourCommand.Execute(null);
}
}
...or by using an interaction trigger in XAML: https://blog.magnusmontin.net/2013/06/30/handling-events-in-an-mvvm-wpf-application/
<Window x:Class="Mm.HandlingEventsMVVM.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
Title="MainWindow" Height="350" Width="525">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded" >
<i:InvokeCommandAction Command="{Binding YourCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Window>
Neither approach breaks the MVVM pattern - both invoke a command of the view model from the view.
I'm trying to call a function in the viewmodel on startup the MVVM way. I thought I had it correct, but the code is never hitting the function call.
Here's my xaml:
<Window x:Class="TestWin.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:l="clr-namespace:Timeserver"
xmlns:viewmodel="clr-namespace:Timeserver.ViewModels"
Title="MainWindow"
Width="893"
Height="Auto"
Background="LightGray"
ResizeMode="NoResize"
SizeToContent="Height">
<Window.DataContext>
<viewmodel:MainWindowViewModel />
</Window.DataContext>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadData}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Here's my viewmodel (the necessary parts):
namespace TestWin.ViewModels
{
class MainWindowViewModel
{
private StructsModel model; // my model class
private ICommand loadDataCmd;
private ICommand showTimeWindowCmd;
private ICommand toggleExecuteCommand { get; set; }
private bool canExecute = true;
public bool CanExecute
{
get
{
return this.canExecute;
}
set
{
if (this.canExecute == value)
{
return;
}
this.canExecute = value;
}
}
public ICommand ToggleExecuteCommand
{
get
{
return toggleExecuteCommand;
}
set
{
toggleExecuteCommand = value;
}
}
public ICommand ShowTimeWindowCmd
{
//code here
}
public ICommand LoadDataCmd
{
get
{
return loadDataCmd;
}
set
{
loadDataCmd = value;
}
}
public void LoadData(object parameter)
{
model.GetData();
}
public MainWindowViewModel()
{
this.model = new StructsModel();
LoadDataCmd = new RelayCommand(LoadData, param => this.canExecute);
ShowTimeWindowCmd = new RelayCommand(ShowTimeWindow, param => this.canExecute);
toggleExecuteCommand = new RelayCommand(ChangeCanExecute);
}
public void ShowTimeWindow(object parameter)
{
//code here
}
public void ChangeCanExecute(object obj)
{
canExecute = !canExecute;
}
}
}
The function in question that is not being hit is LoadData(). It calls GetData() in my model class, which I cannot show for reasons. Not sure what else to try. I've seen other questions on SO doing the same thing I'm doing. My other function ShowTimeWindow is set up the exact same way and does get hit.
If you really want the call to be made at Loaded, you could remove the xaml code
<Window.DataContext>
<viewmodel:MainWindowViewModel />
</Window.DataContext>
and bind the viewmode from code behind, like Mat said above. Then, the Command you have bound will be triggered (if it has the same name as the one in the viewmodel), and you will not need to call the vm.LoadData() in the constructor.
Also, if the command is not used for anything else than to load data at "Loaded", CanExecute is useless.
You can create your ViewModel in code behind. Than you can call the methods you need. If you want to go for excellence you can use a Factory pattern or a dependency injection container (e.g. Windsor)
public MainWindow()
{
InitializeComponent();
MainWindowViewModel vm = new MainWindowViewModel();
DataContext = vm;
vm.LoadData();
}
I'm developing my first application in WPF with the pattern MVC and I have a question.
I have created a usercontrol from type Grid to made a custom title bar. This grid contains a X button and I want to associate this button to a command.
My grid in XAML:
<Grid x:Class="Views.TitleBarView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
Style="{DynamicResource TitleStyleGrid}"
x:Name="barView">
<Label x:Name="labelAppName" Style="{DynamicResource TitleStyleLabel}" Content="Title"/>
<Button x:Name="bttnClose" Style="{DynamicResource ButtonStyleCloseWindow}" Command="{Binding CloseCommand}"/>
<Button x:Name="buttonMinimize" Style="{DynamicResource ButtonStyleMinimizeWindow}" Command="{Binding MinimizeCommand}"/>
</Grid>
The C# view:
public partial class TitleBarView : Grid
{
public TitleBarView()
{
InitializeComponent();
TitleBarViewModel tvm = new TitleBarViewModel();
tvm.RequestClose += (s, e) => this.Close();
tvm.RequestMinimize+= (s, e) => this.Minimize();
DataContext = tvm;
}
private void Close()
{
Window.GetWindow(this).Close();
}
private void Minimize()
{
Application.Current.MainWindow.WindowState = System.Windows.WindowState.Minimized;
}
}
The C# viewModel:
public class TitleBarViewModel : ViewModelBase, IRequestMinimizeViewModel, IRequestCloseViewModel
{
private RelayCommand minimizeCommand;
protected RelayCommand closeCommand;
public event EventHandler RequestMinimize;
public event EventHandler RequestClose;
#region MinimizeCommand
public ICommand MinimizeCommand
{
get
{
if (minimizeCommand == null)
{
minimizeCommand = new RelayCommand(Minimize);
}
return minimizeCommand;
}
}
private void Minimize()
{
RequestMinimize(this, null);
}
#endregion
#region CloseCommand
public ICommand CloseCommand
{
get
{
if (closeCommand == null)
{
closeCommand = new RelayCommand(Close);
}
return closeCommand;
}
}
protected void Close()
{
RequestClose(this, null);
}
#endregion
}
I saw that it's not recommended to set a DataContext on a userControl. And when I do this, I can't change the close command. For example I want that when the main windows calls command close it calls Application.Current.Shutdown(); instead of Application.Current.Shutdown();
I know that I have something wrong but I'm too confuse to solve it. Can you explain me how to create command for userControl ? (Or just tell me what I'm doing wrong)
Thank you
My issue is that I wish to press enter after typing a text in a textbox. When I do, I want this to trigger a specific property in my viewmodel which is defined in my PCL (this can not be changed).
I have seen some examples that almost do the similar thing but they only do standard actions such as clear text in textbox or tab to next control and so on. I want this one to interact with a property of my choice.
HeaderView.xaml
<views:MvxWpfView
xmlns:views="clr-namespace:Cirrious.MvvmCross.Wpf.Views;assembly=Cirrious.MvvmCross.Wpf"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:UI="clr-namespace:ProductCatalog.UserInterface.WPF.Bootstrap"
x:Class="ProductCatalog.UserInterface.WPF.Views.HeaderView">
<Grid Height="70" Background="#005287">
<TextBox DataContext="{Binding SearchText}" UI:TextBoxExtension.EnterKey="Search"
Width="120" Height="35" Padding="8" Margin="10" HorizontalAlignment="Right" >
</TextBox>
</Grid></views:MvxWpfView>
HeaderView.xaml.cs
public partial class HeaderView : MvxWpfView
{
public new HeaderViewModel ViewModel
{
get { return (HeaderViewModel) base.ViewModel; }
set { base.ViewModel = value; }
}
public HeaderView()
{
InitializeComponent();
}
}
HeaderViewModel.cs (In PCL)
public class HeaderViewModel : MvxViewModel
{
private string _searchText;
public string SearchText
{
get { return _searchText; }
set
{
_searchText = value;
RaisePropertyChanged(() => SearchText);
}
}
public ICommand Search
{
get
{
return new MvxCommand(() =>
{
SearchItems = new List<string> { "Hey", "Hello", "Hola" };
});
}
}
private IList<string> _searchItems;
public IList<string> SearchItems
{
get { return _searchItems; }
set { _searchItems = value; RaisePropertyChanged(() => SearchItems); }
}
}
TextBoxExtension.cs
public static class TextBoxExtension
{
public static ICommand GetEnterKey(DependencyObject obj)
{
return (ICommand)obj.GetValue(EnterKey);
}
public static void SetEnterKey(DependencyObject obj, ICommand value)
{
obj.SetValue(EnterKey, value);
}
public static readonly DependencyProperty EnterKey =
DependencyProperty.RegisterAttached("EnterKey", typeof(ICommand), typeof(TextBoxExtension), new UIPropertyMetadata(EnterKeyPropertyChanged));
static void EnterKeyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UIElement element = d as UIElement;
if (element == null)
{
return;
}
element.KeyDown += Keydown;
}
static void Keydown(object sender, KeyEventArgs e)
{
if (!e.Key.Equals(Key.Enter))
{
return;
}
UIElement element = sender as UIElement;
if (element != null)
{
ICommand command = GetEnterKey(element);
command.Execute(null);
}
}
}
Here is what happens, when I use the parameter Search into my UI:TextBoxEntension.EnterKey the event is triggered like I want it to, but it can't find my Search property in my ViewModel. When I try using {Binding Search} then the event is never triggered. How can I get the event to trigger my Search property in my ViewModel.
Many thanks
Have you tried using InputBindings:
<TextBox>
<TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding DoSomething}"/>
</TextBox.InputBindings>
</TextBox>
And in your ViewModel:
DoSomething = new RelayCommand(() => DoSomething(), () => true);
First, you should define your Attached property as
public static readonly DependencyProperty EnterKeyProperty =
DependencyProperty.RegisterAttached("EnterKey", typeof(ICommand), typeof(TextBoxExtension), new UIPropertyMetadata(EnterKeyPropertyChanged));
public static ICommand GetEnterKey(DependencyObject obj)
{
return (ICommand)obj.GetValue(EnterKeyProperty);
}
public static void SetEnterKey(DependencyObject obj, ICommand value)
{
obj.SetValue(EnterKeyProperty, value);
}
This is to differentiate between attached property and CLR property.
Second binding should happen as
<TextBox DataContext="{Binding SearchText}" UI:TextBoxExtension.EnterKey="{Binding Search}"
Width="120" Height="35" Padding="8" Margin="10" HorizontalAlignment="Right" >
</TextBox>
I have a Label the Content of which I would like to update after each second,after 3 seconds I only see the last string "Step 3..." What am I doing wrong and is there another way to achieve this if for some reason I cannot use Thread.Sleep():
View:
<Window x:Class="WpfApplication1.ScrollerView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Scroller" DataContext="{StaticResource scrollerVM}" Height="150" Width="300">
<Grid>
<ListBox ItemsSource="{Binding Messages}" Width="200" Height="50" BorderThickness="0" VerticalAlignment="Top" HorizontalAlignment="Left">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Text}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
<Button Width="70" Height="24" Content="Add new" Command="{Binding AddNew}" HorizontalAlignment="Left" Margin="0,56,0,30" />
</Grid>
</Window>
View model:
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading;
namespace WpfApplication1.Scroller
{
public class Message
{
public Message(string _text)
{
text = _text;
}
private string text;
public string Text
{
get { return text; }
set {text = value;}
}
}
public class ScrollerViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public DelegateCommand AddNew { get; protected set; }
ObservableCollection<Message> _messages = new ObservableCollection<Message>();
public ObservableCollection<Message> Messages
{
get { return _messages; }
set
{
_messages = value;
OnPropertyChanged("Messages");
}
}
public ScrollerViewModel()
{
AddNew = new DelegateCommand(Add);
}
private void Add(object parameter)
{
UpdateProgress("Step 1...");
UpdateProgress("Step 2...");
UpdateProgress("Step 3...");
}
private void UpdateProgress(string step)
{
Messages.Clear();
Messages.Add(new Message(step));
Thread.Sleep(1000);
}
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
It is because you are sleeping in the UI thread. The UI won't have a chance to update until Add is finished. You can use a BackgroundWorker with ReportProgress to achieve what you want. Something like this:
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.DoWork += delegate(object s, DoWorkEventArgs args)
{
worker.ReportProgress(1, "Step1");
Thread.Sleep(1000);
worker.ReportProgress(2, "Step2");
Thread.Sleep(1000);
worker.ReportProgress(3, "Step3");
};
worker.ProgressChanged += delegate(object s, ProgressChangedEventArgs args)
{
string step = (string)args.UserState;
Messages.Clear();
Messages.Add(new Message(step));
};
worker.RunWorkerAsync();
The UI thread won't be occupied while DoWork is executed, but the code in ProgressChanged will be performed on the UI thread.
You should NEVER call Thread.Sleep when you´re on the UI Thread. This will block the Dispatcher and no rendering. binding updates etc. will occur during this time.
If you want to wait between your UpdateProgress() calls, use a DispatcherTimer.
You are clearing the messages before adding the new one.
private void UpdateProgress(string step)
{
Messages.Clear(); // <- Why?
Messages.Add(new Message(step));
Thread.Sleep(1000);
}