I have maximum simple app. I want to fill listbox when button pressed. I use binding, window DataContext is updated after some operation, but UI not updated!
Here is the code:
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button Content="Button" HorizontalAlignment="Left" Margin="432,288.04,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
<ListBox x:Name="urlsListBox" ItemsSource="{Binding Urls}" HorizontalAlignment="Left" Height="300" Margin="10,10,0,0" VerticalAlignment="Top" Width="417"/>
</Grid>
MainWindow.xaml.cs
namespace WpfApplication1
{
public partial class MainWindow : Window
{
ViewModel model = new ViewModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = model;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
model.GetUrls();
}
}
}
ViewModel.cs
namespace WpfApplication1
{
class ViewModel
{
private ObservableCollection<Url> Urls { get; set; }
public ViewModel()
{
Urls = new ObservableCollection<Url>();
}
public void GetUrls()
{
for (int i = 0; i < 5; i++)
{
Urls.Add(new Url { link = i.ToString() });
}
}
}
public class Url
{
public string link { get; set; }
}
}
Problem stems from the Urls property within the ViewModel class. You need to make Urls public, otherwise the MainWindow cannot access the property:
ViewModel:
namespace WpfApplication1
{
public class ViewModel
{
//make this public otherwise MainWindow will not have access to it!
public ObservableCollection<Url> Urls { get; set; }
public ViewModel()
{
Urls = new ObservableCollection<Url>();
}
public void GetUrls()
{
for (int i = 0; i < 5; i++)
{
Urls.Add(new Url { link = i.ToString() });
}
}
}
public class Url
{
public string link { get; set; }
}
}
Hope this helps and let me know if you have any questions!
You need to support property change notification. Use the NuGet package manager to reference the MVVM Lite project, derive your ViewModel from ViewModelBase and then change your link property to this:
private string _link;
{
public string link
{
get {return this._link;}
set {this._link=value; RaisePropertyChanged(() => this.link); }
}
}
You'll also need to do this for your URLs property which needs to be public in order for binding to work. Also I know this is a little bit outside the scope of the question but in general you shouldn't use the Click handler like this, the "proper" way is to add a RelayCommand to your ViewModel and bind your button's Command property to that.
Related
I know there are a lot of similar questions and I spent two hours by now trying to implementing them but can't proceed. So the problem seems simple. When I don't have a viewmodel, I can set the datacontext to a class and it is very easy to transfer data with that class. But when there is viewmodel, I have to set the datacontext to that and can't find a way to return any value after that. I tried to implement countless solutions to the problem but it seems that they are above my skill level. Thank you so much for your help!
The important parts of my code (its a simple game which i want to save, where save is named by userinput) The first window, where I want to get data from the second window
case Key.Escape: {
Thread t = new Thread(() => {
SaveGameWindow pw = new SaveGameWindow(); //the second window
if ((pw.ShowDialog() == true) && (pw.Name != string.Empty)) //pw.Name always empty
{
ILogicSaveGame l = new LogicSaveGame();
l.Write(pw.Name, "saved_games.txt");
MessageBox.Show("game saved");
}
});
t.SetApartmentState(ApartmentState.STA);
t.Start();
XAML (from now on everything belongs to the SaveGameWindow):
<Window.Resources>
<local:SaveGameViewModel x:Key="my_viewmodel"/>
</Window.Resources>
<Grid DataContext="{StaticResource my_viewmodel}">
<TextBox Text="{Binding Name}"/> //i want to acces this in the first window
<Button Command="{Binding CloseCommand}"
Content="Save"/>
Code behind:
private readonly SaveGameViewModel vm;
public SaveGameWindow()
{
this.InitializeComponent();
this.vm = this.FindResource("my_viewmodel") as SaveGameViewModel;
if (this.vm.CloseAction == null)
{
this.vm.CloseAction = new Action(() => this.Close());
}
}
Viewmodel
public class SaveGameViewModel : ViewModelBase
{
public SaveGameViewModel()
{
this.CloseCommand = new RelayCommand(() => this.Close());
}
public string Name { get; set; }
public ICommand CloseCommand { get; private set; }
public Action CloseAction { get; set; }
private void Close()
{
this.CloseAction();
}
}
I use galasoft mvvmlightlibs
There are many solutions to this problem. The simplest solution is to use a shared view model for both windows and data binding. Since both windows would share the same DataContext, both have access to the same data or model instance by simply referencing their DataContext property.
But if you prefer to have individual view models, you would choose a different solution.
Solution 1
If you want to use a dedicated view model for each window, you can always use composition and make e.g. an instance SaveGameViewModel a member of MainWindowViewModel. Any class that has access to MainWindowViewModel will also have access to the SaveGameViewModel and its API, either directly or via delegating properties.
This example uses direct access by exposing SaveGameViewModel as a public property of MainWindowViewModel:
SaveGameViewModel.cs
public class SaveGameViewModel : INotifyPropertyChanged
{
private string name;
public string Name
{
get => this.name;
set
{
this.name = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MainWindowViewModel.cs
public class MainWindowViewModel : INotifyPropertyChanged
{
public SaveGameViewModel SaveGameViewModel { get; set; }
// Allow to create an instance using XAML
public MainWindowViewModel() {}
// Allow to create an instance using C#
public MainWindowViewModel(SaveGameViewModel saveGameViewModel)
=> this.SaveGameViewModel = saveGameViewModel;
}
App.xaml
<Application>
<Application.Resources>
<MainWindowViewModel x:Key="MainWindowViewModel">
<MainWindowViewModel.SaveGameViewModel>
<SaveGameViewModel />
</MainWindowViewModel.SaveGameViewModel>
</MainWindowViewModel>
</Application.Resources>
</Application>
SaveGameWindow.xaml
<Window DataContext="{Binding Source={StaticResource MainWindowViewModel}, Path=SaveGameViewModel}">
<TextBox Text="{Binding Name}" />
<Window>
MainWindow.xaml
<Window DataContext="{StaticResource MainWindowViewModel}">
<Window>
MainWindow.xaml.cs
partial class MainWindow : Window
{
private void OnKeyPressed(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape)
{
var mainWindowViewModel = this.DataContext as MainWindowViewModel;
string saveGameName = mainWindowViewModel.SaveGameViewModel.Name;
}
}
}
Solution 2
Since you are just showing a dialog, you can store the current instance of the SaveGameViewModel or its values of interest after the dialog has been closed:
MainWindow.xaml.cs
partial class MainWindow : Window
{
private SaveGameViewModel CurrentSaveGameViewModel { get; set; }
private bool IsSaveGameValid { get; set; }
private void ShowDialog_OnSaveButtonClick(object sender, RoutedEventArgs e)
{
var saveGameDialog = new SaveGameWindow();
this.IsSaveGameValid = saveGameDialog.ShowDialog ?? false;
this.CurrentSaveGameViewModel = saveGameDialog.DataContext as SaveGameViewModel;
}
private void OnKeyPressed(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape && this.IsSaveGameValid)
{
string saveGameName = this.CurrentSaveGameViewModel.Name;
}
}
}
MainWindow.xaml
<Window>
<Window.DataContext>
<MainWindowViewModel />
</Window.DataContext>
<Window>
SaveGameWindow.xaml
<Window>
<Window.DataContext>
<SaveGameViewModel />
</Window.DataContext>
<TextBox Text="{Binding Name}" />
<Window>
I'm new to Prism and I'm trying to update a text in MainWindow.xaml another view in region.
MainWindowViewModel
private string _message = "Prism";
public string Message
{
get { return _message; }
set { SetProperty(ref _message, value);}
}
MainWindow.xaml
<Window x:Class="XXXX.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
Title="{Binding Title}">
<Grid>
<StackPanel>
<TextBlock Text="{Binding Message, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" FontSize="48"></TextBlock>
<ContentControl prism:RegionManager.RegionName="ViewARegion" />
</StackPanel>
</Grid>
ViewAViewModel
public ICommand ClickCommand
{
get;
private set;
}
public ViewAViewModel()
{
ClickCommand = new DelegateCommand(ClickedMethod);
}
private void ClickedMethod()
{
MainWindowViewModel mainWindowViewModel = new MainWindowViewModel();
mainWindowViewModel.Message = "Prism View A";
}
ViewA.xaml
<UserControl x:Class="XXXX.Views.ViewA"
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"
xmlns:local="clr-namespace:XXXX.Views"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d">
<Grid>
<StackPanel>
<Button Content="Click"
Command="{Binding ClickCommand}">
</Button>
</StackPanel>
</Grid>
Now when I click the button it's working correctly I mean it's setting the Message property in MainWindowViewModel but it's not udating the View in MainWindow.xaml.
What should I do to get this working as I'm expecting to update the view on button click?
MainWindowViewModel mainWindowViewModel = new MainWindowViewModel();
mainWindowViewModel.Message = "Prism View A";
This creates a new instance of MainWindowViewModel that has nothing to do with the instance that's bound to your MainWindow. You can change properties on this new instance all day long, the real view model will not care.
You have to implement some view model to view model communication mechanism, e.g. use IEventAggregator or a shared service, so that the information ("click happened" or "message changed" or whatever) can be passed from ViewA to the MainWindow.
You could use the event aggregator to send an event from ViewAViewModel to MainWindowViewModel:
public class ViewAViewModel
{
private readonly IEventAggregator _eventAggregator;
public ViewAViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
ClickCommand = new DelegateCommand(ClickedMethod);
}
public ICommand ClickCommand
{
get;
private set;
}
private void ClickedMethod()
{
_eventAggregator.GetEvent<PubSubEvent<string>>().Publish("Prism View A");
}
}
public class MainWindowViewModel : BindableBase
{
public MainWindowViewModel(IEventAggregator eventAggregator)
{
eventAggregator.GetEvent<MessageSentEvent>().Subscribe(MessageReceived);
}
private void MessageReceived(string message)
{
Message = message;
}
private string _message = "Prism";
public string Message
{
get { return _message; }
set { SetProperty(ref _message, value); }
}
}
There is a complete example available on GitHub: https://github.com/PrismLibrary/Prism-Samples-Wpf/tree/master/14-UsingEventAggregator
I have a view model which has another class set as its property. The another class contains implementations of ICommand as its properties. I would like to execute one of the commands on a double click.
Unfortunatelly, Caliburn.Micro raises an exception instead ("No target found for method Commands.Command.Execute.").
I've tried to search the net and read the documentation, but without any success.
How to do it correctly?
Note: In real application, the message might be attached to a grid view which might have a different DataContext than the view model containing the commands class.
The XAML:
<Window x:Class="WpfApplication8.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="http://www.caliburnproject.org">
<Grid>
<TextBox
cal:Message.Attach="[Event MouseDoubleClick]
= [Action Commands.Command.Execute(null)]" />
</Grid>
</Window>
The code-behind class:
namespace WpfApplication8
{
public partial class MainWindow
{
public Commands Commands { get; set; }
public MainWindow()
{
this.Commands =
new Commands { Command = new Command { MainWindow = this } };
this.InitializeComponent();
this.DataContext = this;
}
}
public class Commands
{
public Command Command { get; set; }
}
public class Command
{
public MainWindow MainWindow { get; set; }
public void Execute(object parameter)
{
this.MainWindow.Title = "Executed";
}
}
}
As #alik commented, complete solution is:
<Window x:Class="WpfApplication8.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="http://www.caliburnproject.org">
<Grid>
<TextBox
cal:Message.Attach="[Event MouseDoubleClick] = [Action Execute(null)]"
cal:Action.TargetWithoutContext="{Binding Commands.Command}"/>
</Grid>
</Window>
While exploring DI/IOC with Unity with WPF, I came across a question and need your feedback. Please consider the following scenario...
================================================================
public interface IDataServices
{
string GetData();
}
================================================================
public class CopyTextDataServices : IDataServices
{
public string GetData()
{
return "copy text from CopyTextDataServices";
}
}
================================================================
public class TextDataServices : IDataServices
{
public string GetData()
{
return "I am injected by setter property injection";
}
}
================================================================
public interface ITextViewModel
{
string LabelContnet { get; set; }
}
================================================================
public class TextViewModel : ITextViewModel
{
public TextViewModel()
{
LabelContnet = "This is from view model";
}
public string LabelContnet { get; set; }
}
================================================================
public partial class MainWindow : Window
{
public MainWindow(ITextViewModel textViewModel)
{
InitializeComponent();
Loaded += MainWindow_Loaded;
DataContext = textViewModel;
}
[Dependency]
public IDataServices Services { get; set; }
containing the event data.</param>
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
LabelLeft.Content = Services.GetData();
}
}
================================================================
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
IUnityContainer container = new UnityContainer();
container.RegisterType<IDataServices, TextDataServices>();
container.RegisterType<IDataServices, CopyTextDataServices>();
container.RegisterType<ITextViewModel, TextViewModel>();
var window = container.Resolve<MainWindow>();
window.Show();
}
}
================================================================
<Window x:Class="TestAppWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" FontSize="20">
<StackPanel>
<Label Content="{Binding Path=LabelContnet,FallbackValue=Left}" HorizontalAlignment="Left" Name="LabelLeft" />
<Label Content="{Binding Path=LabelContnet,FallbackValue=Right}" HorizontalAlignment="Left" Name="LabelRight" />
</StackPanel>
</Window>
===================================================================
Now the result of this appears in the labels is
copy text from CopyTextDataServices
This is from view model
But I want to know if I want to get data from TextDataServices, how can I do that?
The problem is in this line:
container.RegisterType<IDataServices, TextDataServices>();
// This overwrites the previous mapping.
// All dependencies to IDataServices will use CopyTextDataServices.
container.RegisterType<IDataServices, CopyTextDataServices>();
If you want to have both IDataServices, you'll need to register one or both as named instances.
container.RegisterType<IDataServices, TextDataServices>("TextDataServicesName");
container.RegisterType<IDataServices, CopyTextDataServices>("CopyTextDataServicesName");
In your control:
[Dependency("TextDataServicesName")]
public IDataServices Services { get; set; }
I have been looking at MVVM for the last couple days and thought i would try out a simple example to update a text box with a time. However i'm having a bit of trouble wrapping my head around this. I have a two projects in my Solution.. one i'm calling TimeProvider that right now is just returning Datetime event and another that is called E. Eventually i will use TimeProvider to provide a lot more information but i want to understand something simple first. Can some one please tell me why i'm not geting the gui to update.
namespace E.TimeProvider
{
public interface ITimeSource
{
void Subscribe();
event Action<Time> TimeArrived;
}
}
namespace E.TimeProvider
{
public class Time
{
private DateTime _earthDate;
public Time()
{
}
public Time(DateTime earthDate)
{
this._earthDate = earthDate;
}
public DateTime EarthDate
{
get { return _earthDate; }
set { _earthDate = value; }
}
}
}
namespace E.TimeProvider
{
public class TimeSource : ITimeSource
{
private const int TIMER_INTERVAL = 50;
public event Action<Time> TimeArrived;
private bool subscribe;
public TimeSource()
{
subscribe = false;
Thread timeGenerator = new Thread(new ThreadStart(GenerateTimes));
timeGenerator.IsBackground = true;
timeGenerator.Priority = ThreadPriority.Normal;
timeGenerator.Start();
}
public void Subscribe()
{
if (subscribe)
return;
subscribe = true;
}
private void GenerateTimes()
{
while (true)
{
GenerateAndPublishTimes();
Thread.Sleep(TIMER_INTERVAL);
}
}
private void GenerateAndPublishTimes()
{
DateTime earthDate = DateTime.Now;
Time time = new Time(earthDate);
TimeArrived(time);
}
}
}
Then i have my project E
xaml
<Window x:Class="E.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="300" Width="200" xmlns:my="clr-namespace:Exiled" WindowStyle="None" WindowStartupLocation="CenterScreen" ResizeMode="NoResize">
<Grid>
<my:TimeControl HorizontalAlignment="Left" x:Name="timeControl1" VerticalAlignment="Top" Height="300" Width="200" />
</Grid>
</Window>
<UserControl x:Class="E.TimeControl"
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"
d:DesignHeight="300" d:DesignWidth="200" Background="Black" Foreground="White">
<Grid>
<TextBlock Height="41" HorizontalAlignment="Left" Margin="12,31,0,0" Text="{Binding Path=EarthTime}" VerticalAlignment="Top" Width="176" FontSize="35" TextAlignment="Center" />
</Grid>
</UserControl>
and the rest
namespace E
{
public class TimeControlViewModel : DependencyObject
{
private readonly ITimeSource _source;
public ObservableCollection<TimeViewModel> Times { get; set; }
public TimeControlViewModel()
{
this.Times = new ObservableCollection<TimeViewModel>();
}
public TimeControlViewModel(ITimeSource source)
{
this.Times = new ObservableCollection<TimeViewModel>();
_source = source;
_source.TimeArrived += new Action<Time>(_source_TimeArrived);
}
public void Subscribe()
{
_source.Subscribe();
}
void _source_TimeArrived(Time time)
{
TimeViewModel tvm = new TimeViewModel();
tvm.Time = time;
}
}
}
namespace E
{
class SubscribeCommand
{
private readonly TimeControlViewModel _vm;
public SubscribeCommand(TimeControlViewModel vm)
{
_vm = vm;
}
public void Execute(object parameter)
{
_vm.Subscribe();
}
}
}
namespace E
{
public class TimeViewModel : DependencyObject
{
public TimeViewModel()
{
}
public Time Time
{
set
{
this.EarthDate = value.EarthDate;
}
}
public DateTime EarthDate
{
get { return (DateTime)GetValue(DateProperty); }
set { SetValue(DateProperty, value); }
}
// Using a DependencyProperty as the backing store for Date. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DateProperty =
DependencyProperty.Register("EarthDate", typeof(DateTime), typeof(TimeViewModel), new UIPropertyMetadata(DateTime.Now));
}
}
You've got a few issues here:
You're not seeing an update, because the DataContext of your Window or UserControl is not set to the TimeViewModel instance you create.
Typically, ViewModel instances should not be DependencyObjects. Instead, implement INotifyPropertyChanged. This keeps them from having a dependency on WPF, but still allows them to work properly with MVVM.
I'd recommend reading through a detailed introduction to MVVM, such as the one I wrote here. In particular, you'll need to understand how templating works and the DataContext in order to use binding properly.
For a start it looks like you're binding to EarthTime:
Text="{Binding Path=EarthTime}"
but the property itself is called EarthDate
public DateTime EarthDate