Hi I am new for learning WPF MVVM ,please help a basic question ,thx.
I use a for-loop to create a counter ,but View doesn't get the property changed.
I know I should use INotifyPropertyChanged to make view refresh ,but it still cannot work...
Why Codes below doesn't work?
View:
<Window x:Class="WpfApp5.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:WpfApp5"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:DemoVM/>
</Window.DataContext>
<Grid>
<Button Content="Button" HorizontalAlignment="Left" Margin="321,297,0,0" VerticalAlignment="Top" Height="38" Width="115" Click="Button_Click"/>
<ProgressBar HorizontalAlignment="Left" Height="30" Margin="98,217,0,0" VerticalAlignment="Top" Width="560" Value="{Binding Number, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Text="{Binding Number, UpdateSourceTrigger=PropertyChanged}" Margin="696,217,26,119" FontSize="20"/>
</Grid>
</Window>
ViewModel:
class DemoVM:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
internal void AddNumber()
{
for (int i = 0; i < 100; i++)
{
Number++;
}
}
int _number;
public int Number
{
get { return _number; }
set
{
_number = value;
OnPropertyChanged(nameof(Number));
}
}
}
MainWindow.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
DemoVM demoVM = new DemoVM();
demoVM.AddNumber();
}
}
}
ViewDesign:
View
It looks like you already have set the binding context of the view to the DemoVM view model, so in your button click, you need not instantiate a new one.
private void Button_Click(object sender, RoutedEventArgs e)
{
((DemoVM)DataContext).AddNumber();
}
Related
I'm a newbie in wpf and i know that this question has been asked other times, and i tried to implement some solutions that i found. But it's not working. I'm doing something wrong but i can't see what it is.
I've created a new simple application to test this problem.
namespace WpfApp3
{
public class MyElement
{
public string Text { get; set; }
public MyElement(string t)
{
Text = t;
}
}
public class MyCommand : ICommand
{
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_handler(parameter);
}
private Action<object> _handler;
public MyCommand(Action<object> handler) { _handler = handler; }
}
public class MyItemsControlViewModel
{
ObservableCollection<MyElement> _items;
public ObservableCollection<MyElement> MyElementItems { get { return _items; } set { _items = value; RaisePropertyChanged("MyElementItems"); } }
ObservableCollection<MyElement> _temporayList;
private ICommand _itemClicked;
public ICommand ItemClicked { get { return _itemClicked; } }
public MyItemsControlViewModel()
{
_items = new ObservableCollection<MyElement>();
_temporayList = new ObservableCollection<MyElement>();
_itemClicked = new MyCommand(OnItemSelected);
AddItem("Element 1");
AddItem("Element 2");
AddItem("Element 3");
UpdateList();
}
public void UpdateList()
{
MyElementItems = _temporayList;
}
public void AddItem(string t)
{
MyElement item = new MyElement(t);
_temporayList.Add(item);
}
public void OnItemSelected(object param)
{
Debug.WriteLine("Executed!");
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
XAML
<UserControl x:Class="WpfApp3.MyUserControl"
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:WpfApp3"
mc:Ignorable="d"
d:DesignHeight="1080" d:DesignWidth="570"
x:Name="myCustomControl">
<Grid >
<Button x:Name="btnOutsideItemsControl" Width="100" Height="100 " VerticalAlignment="Top" Command="{Binding ItemClicked}" />
<ItemsControl
x:Name="listItems"
ScrollViewer.PanningMode="None"
IsEnabled="False"
Background = "Transparent"
HorizontalAlignment="Center"
ItemsSource="{Binding MyElementItems}" Margin="0,152,0,0" Width="549">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel HorizontalAlignment="Left" Margin="50,0,0,0"
Background="Transparent" Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button
Content="{Binding Text}"
Command="{Binding ElementName=listItems, Path=DataContext.ItemClicked}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
The component is used in MainWindow.xaml.
namespace WpfApp3
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private MyItemsControlViewModel _myViewModel;
public MyItemsControlViewModel MyViewModel { get { return _myViewModel; } }
public MainWindow()
{
_myViewModel = new MyItemsControlViewModel();
InitializeComponent();
myCustomControl.DataContext = MyViewModel;
}
}
}
XAML
<Window x:Class="WpfApp3.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:WpfApp3"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<local:MyUserControl x:Name="myCustomControl"/>
</Grid>
</Window>
When i run the application i can see correctly the list of 3 items with the correct text.
But if i click on one of the button of the list i can't see the output of Debug.WriteLine("Executed!");
But if i click on the button btnOutsideItemsControl that is outside the ItemsControl, it works. I can see the output of Debug.WriteLine("Executed!");
So i think that also the definition of the command is correct.
To bind correctly the Command property of Button inside the ItemsControl i try this
<Button Command="{Binding ElementName=listItems, Path=DataContext.ItemClicked}">
And also this
<Button Command="{Binding DataContext.ItemClicked, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ItemsControl}}">
But it not works.
Please help!
You're gonna kick yourself once I tell you.
Your problem is that you set IsEnabled="False" on your ItemsControl. Remove it and all will be well with the universe.
I'm new to WPF, and the code I've written doesn't seem to be working. I'm trying to move a rectangular paddle to the left when the left arrow key is pressed, but I get no response when I press the Left arrow key. What am I doing wrong here?
Here is the View:
<UserControl.InputBindings>
<KeyBinding Key="Left" Command="{Binding MovePaddleLeftCommand}"/>
</UserControl.InputBindings>
and here is the code in the ViewModel:
private ICommand _movePaddleLeftCommand;
public ICommand MovePaddleLeftCommand
{
get
{
return new DelegateCommand(ExecuteCommand, CanExecute);
}
}
private void ExecuteCommand()
{
MessageBox.Show("Left");
MovePaddleLeft();
}
private bool CanExecute()
{
return true;
}
Below is a more complete picture of the code in case it's relevant.
GameView.xaml:
<UserControl x:Class="Arkanoid_MkII.Views.GameView"
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:Arkanoid_MkII.Views"
mc:Ignorable="d"
Width="1500" Height="800">
<Canvas Name="GameArea" ClipToBounds="True">
<Rectangle Canvas.Left="{Binding Path=Paddle.PaddleX}" Canvas.Top="750" Width="{Binding Path=Paddle.Width}" Height="{Binding Path=Paddle.Height}" Fill="{Binding Path=Paddle.Colour}"/>
</Canvas>
<UserControl.InputBindings>
<KeyBinding Key="Left" Command="{Binding MovePaddleLeftCommand}"/>
</UserControl.InputBindings>
</UserControl>
GameView.xaml.cs:
public partial class GameView : UserControl
{
public GameView()
{
InitializeComponent();
}
}
MainWindow.xaml:
<Window x:Class="Arkanoid_MkII.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:Arkanoid_MkII"
xmlns:views="clr-namespace:Arkanoid_MkII.Views"
mc:Ignorable="d"
Title="MainWindow" SizeToContent="WidthAndHeight" ResizeMode="NoResize" Background="Black" Padding="100">
<Grid>
<Border BorderBrush="Black" BorderThickness="5">
<views:GameView x:Name="GameViewControl" Loaded="GameViewControl_Loaded" />
</Border>
</Grid>
</Window>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void GameViewControl_Loaded(object sender, RoutedEventArgs e)
{
Arkanoid_MkII.ViewModels.GameViewModel gameViewModelObject = new Arkanoid_MkII.ViewModels.GameViewModel();
GameViewControl.DataContext = gameViewModelObject;
}
}
GameViewModel.cs:
private ICommand _movePaddleLeftCommand;
public ICommand MovePaddleLeftCommand
{
get
{
return new DelegateCommand(ExecuteCommand, CanExecute);
}
}
private void ExecuteCommand()
{
MessageBox.Show("Left");
MovePaddleLeft();
}
private bool CanExecute()
{
return true;
}
The UserControl must be focusable and focused for the command to be executed:
private void GameViewControl_Loaded(object sender, RoutedEventArgs e)
{
Arkanoid_MkII.ViewModels.GameViewModel gameViewModelObject =
new Arkanoid_MkII.ViewModels.GameViewModel();
GameViewControl.DataContext = gameViewModelObject;
GameViewControl.Focusable = true;
Keyboard.Focus(GameViewControl);
}
Here is the method:
private void Capitales_SelectedChanged(object sender, RoutedEventArgs e)
{
string s = Capitales.SelectedItem.ToString();
tb.Text = "Selection: " + s;
}
I'm putting a list in the combobox, and when I compile the program, the textbox shows the next: ComboBox_MicroDocu.MainWindow+Ciudades, where "Ciudades" references my class.
You are writing WPF app as you whould do with Winform. This whould work, but there is a better way to do it. Use MVVM (Model View ViewModel).
MVVM is great since it allows you to decouple your views (xaml) from your business logic (viewModels). It's also great for testability.
Check out some good resources here https://www.tutorialspoint.com/mvvm/index.htm
this is how your code should looks like :
MainWindow.xaml
<Window x:Class="WpfApp1.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:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ComboBox Grid.Column="0" ItemsSource="{Binding Elements}" SelectedItem="{Binding SelectedElement}"/>
<TextBox Grid.Column="1" Text="{Binding SelectedElement}"/>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
ViewModel.cs
public class ViewModel : INotifyPropertyChanged
{
private string _selectedElement;
public IEnumerable<string> Elements
{
get
{
for(int i = 0; i < 10; i++)
{
yield return $"Element_{i}";
}
}
}
public string SelectedElement
{
get
{
return _selectedElement;
}
set
{
_selectedElement = value;
RaisePropertyChanged();
}
}
private void RaisePropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
I am trying bind an int to a label in a simple application. The idea is, when a button is pressed, the int, and hence the label, is updated.
I have simplified the code as much as I can but I am not able to see the problem.
I think the issue is with NotifyPropertyChanged(String propertyName) as at runtime start the label's content is updated with the value of the int. However, when the int updates, the label does not.
MainWindow.xaml
<Window x:Class="Press.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"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="Presses: "/>
<Label Content="{Binding Path=PressCount}"/>
</StackPanel>
<Button Content="Press Me" Click="PressMe_Click"/>
</StackPanel>
</Window>
MainWindow.xaml.cs
using System;
using System.Windows;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Press
{
public partial class MainWindow : Window
{
public int pressCount = 0;
public int PressCount {
get {
return pressCount;
}
private set {
if(value != pressCount)
{
pressCount = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void PressMe_Click(object sender, RoutedEventArgs e)
{
PressCount++;
Console.WriteLine(PressCount);
}
}
}
You need MainWindow to implement the interface INotifyPropertyChanged
So I just started playing around with WPF using MVVM and encountered a problem.
I want to create a simple program which takes a list of firstnames and surnames and puts together a random name. Everything works except that the name is not shown in the application window.
The button-command is correctly delegated to my function and the PropertyChanged Event is raised when I debug.
My code looks like this:
ViewModel:
class NamePicker: ObservableObject
{
private string _name;
public string Name
{
get{return _name;}
set
{
_name = value;
RaisePropertyChangedEvent("Name");
}
}
public ICommand CreateNameCommand
{
get { return new DelegateCommand(CreateName); }
}
public void CreateName()
{
Random rnd =new Random();
Name = randomNames.firstName[rnd.Next(0, randomNames.firstName.Length - 1)] + " "
+ randomNames.surName[rnd.Next(0, randomNames.surName.Length - 1)];
}
}
ObservableObject:
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChangedEvent(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML:
<Window
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:MVVM_Beispiel"
xmlns:ViewModels="clr-namespace:MVVM_Beispiel.ViewModels" x:Class="MVVM_Beispiel.MainWindow"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button Command="{Binding CreateNameCommand}" Content="Name ausgeben" HorizontalAlignment="Left" Margin="202,292,0,0" VerticalAlignment="Top" Width="112">
<Button.DataContext>
<ViewModels:NamePicker/>
</Button.DataContext>
</Button>
<Label Content="Namensgenerator" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="202,10,0,0" Width="112"/>
<Image HorizontalAlignment="Left" Height="183" Margin="140,41,0,0" VerticalAlignment="Top" Width="244" Source="/MVVM-Beispiel;component/img/007.jpg"/>
<TextBlock HorizontalAlignment="Left" Margin="140,251,0,0" TextWrapping="Wrap" Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="244">
<TextBlock.DataContext>
<ViewModels:NamePicker/>
</TextBlock.DataContext>
</TextBlock>
</Grid>
DelegateCommand:
public class DelegateCommand : ICommand
{
private readonly Action _action;
public DelegateCommand(Action action)
{
_action = action;
}
public void Execute(object parameter)
{
_action();
}
public bool CanExecute(object parameter)
{
return true;
}
}
So what exactly am I missing that the TextBlock doesn't show the current generated name? I read some tutorials but those don't go into detail about everything and it seems like I should find a good book about MVVM.
You have two different instances of NamePicker: first in Button and second one in TextBlock. So Name is changed only in first one. Remove all this stuff:
<Button.DataContext>
<ViewModels:NamePicker/>
</Button.DataContext>
<TextBlock.DataContext>
<ViewModels:NamePicker/>
</TextBlock.DataContext>
and set one view model to entire window:
<Window.DataContext>
<ViewModels:NamePicker/>
</Window.DataContext>