I have created an UserControl which holds several labels. On the other side, I have a window (simple grid) which creates 6 instances of UserControl and places them in a single row.
My question is: how to fire up some action when user clicks on userControl (any part of it)?
I've tried to add MouseDown event handler inside UserControl cs file, and inside parent window during UserControl instance creation, but this doesn't have any effect. I've also tried adding PreviewMouseLeftButtonDown, MouseEnter, MouseLeftButtonDownButton but nothing of these worked.
This is part of parent window:
public BuyerSellerMonitorGridWindow()
{
InitializeComponent();
for (int i = 0; i < 6; i++)
{
// Add new column to grid
this.grdBuyer.ColumnDefinitions.Add(new ColumnDefinition());
// Create and add new transaction
UserControl uc = new UserControl();
uc.PreviewMouseLeftButtonDown += uc_MouseDown;
System.Windows.Controls.Grid.SetRow(uc, 0);
System.Windows.Controls.Grid.SetColumn(uc, i);
this.grdBuyer.Children.Add(uc);
}
}
This is UserControl:
public partial class Transaction: UserControl
{
public Transaction()
{
InitializeComponent();
}
private void TransactionClicked()
{
Console.WriteLine("test");
}
}
And this is xaml:
<UserControl x:Class="AssetStudio.Dialogs.Transaction"
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:AssetStudio.Dialogs"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel x:Name="frameTransaction">
<Label x:Name="lblClientName" Content="Client A" Height="35"/>
<Label x:Name="lblDate" Content="2017/05/01"/>
<Grid Height="34">
<Label x:Name="label" Content="Curr Px" HorizontalAlignment="Left" Width="150"/>
<Label x:Name="lblCurrentPrice" Content="Label" Margin="150,0,0,0"/>
</Grid>
<Grid Height="34">
<Label x:Name="label2" Content="Trade Px" HorizontalAlignment="Left" Width="150"/>
<Label x:Name="lblTradePrice" Content="Label" Margin="150,0,0,0"/>
</Grid>
<Grid Height="34">
<Label x:Name="label4" Content="Qty Done" HorizontalAlignment="Left" Width="150"/>
<Label x:Name="lblQuantityDone" Content="Label" Margin="150,0,0,0"/>
</Grid>
<Grid Height="34">
<Label x:Name="label6" Content="Size" HorizontalAlignment="Left" Width="150"/>
<Label x:Name="lblSize" Content="Label" Margin="150,0,0,0"/>
</Grid>
<Grid Height="34">
<Label x:Name="label8" Content="Stk Px" HorizontalAlignment="Left" Width="150"/>
<Label x:Name="lblStockPrice" Content="Label" Margin="150,0,0,0"/>
</Grid>
<Grid Height="34">
<Label x:Name="label10" Content="Delta" HorizontalAlignment="Left" Width="150"/>
<Label x:Name="lblDelta" Content="Label" Margin="150,0,0,0"/>
</Grid>
</StackPanel>
</UserControl>
You can do something like this:
in your UserControl:
include namespaces:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
and add CallMethodAction
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<ei:CallMethodAction MethodName="UserControlClicked"
TargetObject="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
TargetObject="{Binding}" tells you that this UserControlClicked method needs to be in your ViewModel that is DataContext of that UserControl.
where UserControlClicked is public method that you call when MouseLeftButtonDown is made on that control.
this is my UserControlViewModel just to show purpose,and to test.
public class UserControlViewModel : INotifyPropertyChanged
{
public UserControlViewModel()
{
Text = "Started!";
}
private string _text;
public string Text
{
get { return _text; }
set
{
_text = value;
OnPropertyChanged();
}
}
public void UserControlClicked()
{
Text = "Clicked!";
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Inside MainWindow i have instanced couple of this UserControl-s:
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel>
<local:UserControl1 />
<local:UserControl1 />
<local:UserControl1 />
<local:UserControl1 />
<local:UserControl1 />
</StackPanel>
</Grid>
And this seems to be working as expected. UserControl that has beed clicked changed label content from Started! to Clicked!.
EDIT: Check this on how to add CallMethodAction via Blend:
CallMethodAction
Related
I am rewriting import masks that have a lot in common, so I want (and must) use inheritance.
I have a basic UserControl with all common controls: (I have left out the grid definitions)
BaseClass.xaml
<UserControl x:Class="BaseImport.BaseClass"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<UserControl.Template>
<ControlTemplate TargetType="UserControl">
<Grid>
<Border Grid.Row="0" Grid.Column="0">
<StackPanel>
<Label Content="Text1:"/>
<ComboBox Name="cbText1" MinWidth="80"/>
</StackPanel>
</Border>
<Border Grid.Row="0" Grid.Column="1">
<StackPanel>
<Label Content="Text2:"/>
<ComboBox Name="cbText2" MinWidth="80"/>
</StackPanel>
</Border>
<Border Grid.Row="0" Grid.Column="2">
<StackPanel>
<ContentPresenter ContentSource="Content"/> <!-- ContentSource="Content" is the default-->
</StackPanel>
</Border>
<!-- next Row -->
<Border Grid.Row="1" Grid.Column="0">
<StackPanel>
<Label Content="Text3:"/>
<TextBox Name="tbText3" TextWrapping="Wrap" Text="" MinWidth="80" VerticalAlignment="Center"/>
</StackPanel>
</Border>
<Border Grid.Row="1" Grid.Column="1">
<StackPanel>
<ContentPresenter/>
</StackPanel>
</Border>
</Grid>
</ControlTemplate>
</UserControl.Template>
</UserControl>
This is a kind of Template that gets "used" like this:
MainWindow.xaml (just for demonstration a mainwindow)
<Window x:Class="zzz.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:my="clr-namespace:BaseImport;assembly=BaseImport"
mc:Ignorable="d"
Title="MainWindow" Height="280" Width="600">
<my:BaseClass>
<StackPanel>
<Label Content="Test:"/>
<ComboBox ItemsSource="{Binding TestTyps}" MinWidth="80"/>
</StackPanel>
</my:BaseClass>
</Window>
MainWindow.xaml.cs
using WpfApp1.ViewModel;
namespace zzz
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
}
}
and to wrap it up MainViewModel.cs:
namespace WpfApp1.ViewModel
{
public class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public string[] TestTyps { get { return new string[] { "abc", "123", "xyz" }; } }
}
}
If I have one ContentPresenter everything works fine. But in the BaseClass I have two, potentially more.
Like this only the "last" Presenter gets populated. And in MainWindow.xaml can only be one declared.
How can I put more Content in MainWindow.xaml?
How can I select the right one?
Thanks
The red rectangle is were the second presenter is located (row 1, column 1) but I want it to be were the arrow points (row 0, column 2).
I want another control in place of the red rectangle also declared in MainWindow.xaml.
The stuff you put directly in the <my:BaseClass> tags is the contentProperty which can be only one.
Why only the last ContentPresenter shows it? Because each VisualElement can only have one parent, so the last one claiming it wins.
However you can create as many properties as you want.
<UserControl x:Class="BaseImport.BaseClass"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<UserControl.Template>
<ControlTemplate TargetType="UserControl">
<Grid>
<Border Grid.Row="0" Grid.Column="0">
<StackPanel>
<Label Content="Text1:"/>
<ComboBox Name="cbText1" MinWidth="80"/>
</StackPanel>
</Border>
<Border Grid.Row="0" Grid.Column="1">
<StackPanel>
<Label Content="Text2:"/>
<ComboBox Name="cbText2" MinWidth="80"/>
</StackPanel>
</Border>
<Border Grid.Row="0" Grid.Column="2">
<StackPanel>
<ContentPresenter ContentSource="FirstContent"/>
</StackPanel>
</Border>
<!-- next Row -->
<Border Grid.Row="1" Grid.Column="0">
<StackPanel>
<Label Content="Text3:"/>
<TextBox Name="tbText3" TextWrapping="Wrap" Text="" MinWidth="80" VerticalAlignment="Center"/>
</StackPanel>
</Border>
<Border Grid.Row="1" Grid.Column="1">
<StackPanel>
<ContentPresenter ContentSource="SecondContent"/>
</StackPanel>
</Border>
</Grid>
</ControlTemplate>
</UserControl.Template>
</UserControl>
<my:BaseClass>
<my:BaseClass.FirstContent>
<StackPanel>
<Label Content="Test:"/>
<ComboBox ItemsSource="{Binding TestTyps}" MinWidth="80"/>
</StackPanel>
<my:BaseClass.FirstContent>
<my:BaseClass.SecondContent>
<StackPanel>
<Label Content="Test10:"/>
<TextBox Text="{Binding Whatever}" MinWidth="80"/>
</StackPanel>
<my:BaseClass.SecondContent>
</my:BaseClass>
I gave the point to Firo, because he gave me the right answer. But I want to give some final thoughts, so everyone can benefit from it.
For the xaml "base-class":
<UserControl
x:Class="BaseImport.BaseClass"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<UserControl.Template>
<ControlTemplate TargetType="UserControl">
<Grid>
<Border Grid.Row="0" Grid.Column="0">
<StackPanel>
<Label Content="Text1:"/>
<ComboBox Name="cbText1" MinWidth="80"/>
</StackPanel>
</Border>
<Border Grid.Row="0" Grid.Column="1">
<StackPanel>
<Label Content="DB:"/>
<ComboBox
Name="cbxDB"
ItemsSource="{Binding DBs}"
SelectedItem="{Binding DB}"
MinWidth="80"
SelectionChanged="selectionChanged"
Loaded="DB_Loaded">
<!--
here the DataContext can be set differently
<ComboBox.DataContext>
<Views:DBViewModel/>
</ComboBox.DataContext>
-->
</ComboBox>
</StackPanel>
</Border>
<!-- the content placeholder -->
<Border Grid.Row="0" Grid.Column="2">
<StackPanel MinWidth="80">
<ContentControl
Content="{Binding ContentOne}"
ContentTemplate="{Binding ContentOneTemplate}"
ContentTemplateSelector="{Binding ContentOneTemplateSelector}"/>
</StackPanel>
</Border>
<Border Grid.Row="0" Grid.Column="3">
<StackPanel MinWidth="80">
<ContentControl
Content="{Binding ContentTwo}"
ContentTemplate="{Binding ContentTwoTemplate}"
ContentTemplateSelector="{Binding ContentTwoTemplateSelector}"/>
</StackPanel>
</Border>
</Grid>
</ControlTemplate>
</UserControl.Template>
</UserControl>
For the "base-class":
public partial class BaseClass : UserControl
{
public BaseClass(BaseViewModel _bvm)
{
InitializeComponent();
// set DataContext for all other controls
DataContext = _bvm;
}
#region DependencyProperty
public static readonly DependencyProperty ContentOneProperty = DependencyProperty.Register("ContentOne", typeof(object), typeof(BaseImportClass));
public static readonly DependencyProperty ContentOneTemplateProperty = DependencyProperty.Register("ContentOneTemplate", typeof(DataTemplate), typeof(BaseImportClass));
public static readonly DependencyProperty ContentOneTemplateSelectorProperty = DependencyProperty.Register("ContentOneTemplateSelector", typeof(DataTemplateSelector), typeof(BaseImportClass));
public static readonly DependencyProperty ContentTwoProperty = DependencyProperty.Register("ContentTwo", typeof(object), typeof(BaseImportClass));
public static readonly DependencyProperty ContentTwoTemplateProperty = DependencyProperty.Register("ContentTwoTemplate", typeof(DataTemplate), typeof(BaseImportClass));
public static readonly DependencyProperty ContentTwoTemplateSelectorProperty = DependencyProperty.Register("ContentTwoTemplateSelector", typeof(DataTemplateSelector), typeof(BaseImportClass));
public object ContentOne
{
get { return GetValue(ContentOneProperty); }
set { SetValue(ContentOneProperty, value); }
}
public DataTemplate ContentOneTemplate
{
get { return (DataTemplate)GetValue(ContentOneTemplateProperty); }
set { SetValue(ContentOneTemplateProperty, value); }
}
public DataTemplateSelector ContentOneTemplateSelector
{
get { return (DataTemplateSelector)GetValue(ContentOneTemplateSelectorProperty); }
set { SetValue(ContentOneTemplateSelectorProperty, value); }
}
public object ContentTwo
{
get { return GetValue(ContentTwoProperty); }
set { SetValue(ContentTwoProperty, value); }
}
public DataTemplate ContentTwoTemplate
{
get { return (DataTemplate)GetValue(ContentTwoTemplateProperty); }
set { SetValue(ContentTwoTemplateProperty, value); }
}
public DataTemplateSelector ContentTwoTemplateSelector
{
get { return (DataTemplateSelector)GetValue(ContentTwoTemplateSelectorProperty); }
set { SetValue(ContentTwoTemplateSelectorProperty, value); }
}
#endregion
}
For the "derived" class xaml:
<UserControl x:Class="WpfApp1.DerivedClass"
xmlns:base="clr-namespace:BaseImport.Views;assembly=BaseImport"
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">
<StackPanel>
<base:BaseClass Name="bc">
<base:BaseClass.ContentOneTemplate>
<DataTemplate>
<StackPanel>
<Label Content="Test:"/>
<ComboBox
ItemsSource="{Binding TestTyps}"
SelectedItem="{Binding TestTyp}"
SelectionChanged="TestTypChanged"
Loaded="TestTypLoaded">
<!--
here the DataContext can be set differently
<ComboBox.DataContext>
<Views:ViewModelXYZ/>
</ComboBox.DataContext>
-->
</ComboBox>
</StackPanel>
</DataTemplate>
</base:BaseClass.ContentOneTemplate>
</base:BaseClass>
</StackPanel>
</UserControl>
For the "derived" class:
public partial class DerivedClass : UserControl
{
ViewModelABC vmabc = null;
public DerivedClass(ViewModel _vm) : this()
{
vmabc = _vm;
this.DataContext = vmabc;
bc.DataContext = vmabc; // or another viewmodel that holds TestTyps and TestTyp or leave it for ViewModelXYZ
}
private void TestTypChanged(object sender, SelectionChangedEventArgs e)
{
PropertyChanged();
}
private void TestTypLoaded(object sender, RoutedEventArgs e)
{
(sender as ComboBox).SelectedValue = (this.DataContext as ViewModel(XYZ/ABC).TestTyp;
}
}
That is what I came up.
I hope that helps others.
In the debug mode, goes from constructor to the set method, but in the get method does not enter at any point, which I think is the problem, but I don't know how to solved.
In the .xaml file I defined the button.
<dx:ThemedWindow
x:Class="ProductsModule.View.ProductView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
Title="MenuView" Height="800" Width="1000">
<Grid Margin="20" VerticalAlignment="Top" Background="White" Height="420" DataContext="{Binding Detail}">
<Grid.Effect>
<DropShadowEffect BlurRadius="20" ShadowDepth="1"/>
</Grid.Effect>
<StackPanel Margin="750 70 70 70" HorizontalAlignment="Left">
<TextBlock Text="{Binding Name}" FontSize="18" Margin="0 5" Foreground="#FF6A6A6A"/>
<Button Command="{Binding ShowCommand }">Add data</Button>
</StackPanel>
</Grid>
</dx:ThemedWindow>
In .xaml.cs file I defined datacontext.
public SecondView()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
VIEW MODEL
public ViewModel()
{
ShowCommand = new DelegateCommand(ShowMethod);
}
public ICommand ShowCommand
{
get;
set;
}
private void ShowMethod()
{
OrderView orderView = new OrderView();
orderView.Show();
}
When I press the button, ShowMethod() is not called and does not do anything.
So I am facing a tricky issue. This is my view model:
namespace Market.ViewModel
{
public class BillingViewModel : ViewModelBase
{
#region Private Members
private Customer _customer;
private Product _products;
private string _productId = "asd";
RelayCommand _numClickedCommand;
#endregion
public Customer Customer
{
get { return _customer; }
set
{
_customer = value;
NotifyPropertyChanged("Customer");
}
}
public Product Products
{
get { return _products; }
set
{
_products = value;
NotifyPropertyChanged("Products");
}
}
public bool CanClick
{
get { return true; }
}
public string ProductId
{
get { return _productId; }
set
{
_productId = value;
NotifyPropertyChanged("ProductId");
}
}
public ICommand NumClickedCommand
{
get
{
if (_numClickedCommand == null)
{
_numClickedCommand = new RelayCommand(param => this.NumClicked(param.ToString()),
param => this.CanClick);
}
return _numClickedCommand;
}
}
#region PrivateMethods
private void NumClicked(string numClicked)
{
ProductId = ProductId+numClicked;
}
#endregion
}
}
It inherits ViewModelBase which implements INotifyPropertyCanged.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And my view is:
<Window x:Class="Billing.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:controls="clr-namespace:Billing"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModel="clr-namespace:Market.ViewModel;assembly=Market.ViewModel"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<viewModel:BillingViewModel x:Key="ViewModel" />
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource ViewModel}}">
<controls:NumPad HorizontalAlignment="Left" Margin="265,205,0,-192" VerticalAlignment="Top" Height="307" Width="242"/>
<TextBox HorizontalAlignment="Left" Height="23" Margin="247,29,0,0" TextWrapping="Wrap" Text="{Binding ProductId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="120"/>
</Grid>
</Window>
NumClickedCommand is used in this xaml:
<UserControl x:Class="Billing.NumPad"
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:viewModel="clr-namespace:Market.ViewModel;assembly=Market.ViewModel"
mc:Ignorable="d" Height="109" Width="248">
<UserControl.Resources>
<viewModel:BillingViewModel x:Key="ViewModel"/>
</UserControl.Resources>
<Grid Height="109" VerticalAlignment="Top" DataContext="{Binding Source={StaticResource ViewModel}}">
<Button Content="1" HorizontalAlignment="Left" Margin="10,0,0,0" VerticalAlignment="Top" Width="75" CommandParameter="1" Command="{Binding NumClickedCommand}"/>
<Button Content="2" HorizontalAlignment="Left" Margin="90,0,0,0" VerticalAlignment="Top" Width="75" CommandParameter="2" Command="{Binding NumClickedCommand}"/>
<Button Content="3" HorizontalAlignment="Left" Margin="170,0,0,0" VerticalAlignment="Top" Width="75" CommandParameter="3" Command="{Binding NumClickedCommand}"/>
<Button Content="4" HorizontalAlignment="Left" Margin="10,27,0,0" VerticalAlignment="Top" Width="75" CommandParameter="4" Command="{Binding NumClickedCommand}"/>
<Button Content="5" HorizontalAlignment="Left" Margin="90,27,0,0" VerticalAlignment="Top" Width="75" CommandParameter="5" Command="{Binding NumClickedCommand}"/>
<Button Content="6" HorizontalAlignment="Left" Margin="170,27,0,0" VerticalAlignment="Top" Width="75" RenderTransformOrigin="1.034,2.171" CommandParameter="6" Command="{Binding NumClickedCommand}"/>
<Button Content="7" HorizontalAlignment="Left" Margin="10,54,0,0" VerticalAlignment="Top" Width="75" CommandParameter="7" Command="{Binding NumClickedCommand}"/>
<Button Content="8" HorizontalAlignment="Left" Margin="90,54,0,0" VerticalAlignment="Top" Width="75" CommandParameter="8" Command="{Binding NumClickedCommand}"/>
<Button Content="9" HorizontalAlignment="Left" Margin="170,54,0,0" VerticalAlignment="Top" Width="75" CommandParameter="9" Command="{Binding NumClickedCommand}"/>
<Button Content="0" HorizontalAlignment="Left" Margin="10,81,0,0" VerticalAlignment="Top" Width="75" CommandParameter="0" Command="{Binding NumClickedCommand}"/>
<Button Content="Submit" HorizontalAlignment="Left" Margin="90,81,0,0" VerticalAlignment="Top" Width="155" />
</Grid>
</UserControl>
The problem is that updaditing ProductId in the viewmodel doesnt reflect in the view. The initial value of asd is updated at the launch of the application. The controls contain set of buttons that implements ICommand interface and it all calls the NumClicked() in viewmodel. While debugging if i click the button NumClicked() is called then ProductId is updated and NotifyPropertyChanged() is also called but UI does not update, it remains the same. But in case i directly update the UI i.e i enter some value in the textbox the same flow happens, PropertyChanged() is called and after that get is also called to update the value in the viewmodel.
I have gone through many such questions already available but not able to get what exactly is stopping the update of the UI. Any help is appreciated and do ask if anything is missing.
Thank You.
DataContext of Grid and TextBox are bound to a view model instance from Window resources.
NumPad control declares its own instance of view model
NumClickedCommand works with wrong data, not with the displayed object
make sure you have only one instance of view model
NumPad inherits DataContext and shouldn't create a new object and change DataContext
<UserControl x:Class="Billing.NumPad"
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:viewModel="clr-namespace:Market.ViewModel;assembly=Market.ViewModel"
mc:Ignorable="d" Height="109" Width="248">
<Grid Height="109" VerticalAlignment="Top">
<!--all Buttons here-->
</Grid>
you are passing same viewmodel into numpadUser controll and mainWindow also as DataContext, so that will create new instance of viewmodel,so according to me you can use mainwindow's DataContext in NumPad also using Minwindow's GridName, you have to name your grid like
<Grid DataContext="{Binding Source={StaticResource ViewModel}}" x:Name="grdNumPad">
and you can access that DataContext in your NumPad.XAML this way
<Button Content="1" HorizontalAlignment="Left" Margin="10,0,0,0" VerticalAlignment="Top" Width="75" CommandParameter="1" Command="{Binding DataContext.NumClickedCommand,ElementName=grdNumPad}"/>
I've created an example based on MVVM
Main window XAML:
<Window x:Class="LearnMVVM.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:LearnMVVM"
xmlns:System="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Window.Resources>
<ObjectDataProvider x:Key="operationTypeEnum" MethodName="GetValues" ObjectType="{x:Type System:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:OperationType"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<DataTemplate DataType="{x:Type local:SomeUserControlViewModel}">
<local:SomeUserControl />
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="25"/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="25"/>
<RowDefinition Height="25"/>
<RowDefinition />
</Grid.RowDefinitions>
<TextBox Grid.Column="0" Grid.Row="0" Margin="2" Text="{Binding Path=A, Mode=TwoWay}"/>
<TextBlock Grid.Column="1" Grid.Row="0" Text="+" TextAlignment="Center" VerticalAlignment="Center" Height="16" Margin="0,4,0,5"/>
<TextBox Grid.Column="2" Grid.Row="0" Margin="2" Text="{Binding Path=B, Mode=TwoWay}"/>
<Button Grid.Column="3" Grid.Row="0" Margin="2" Content="Посчитать" Command="{Binding ClickCommand}"/>
<TextBox Grid.Column="4" Grid.Row="0" Margin="2" IsReadOnly="True" Text="{Binding Path=Summa, Mode=TwoWay}"/>
<ComboBox Grid.Column="2" Grid.Row="1" SelectedItem="{Binding Path=SomeUserControl.Operation, Mode=TwoWay}" ItemsSource="{Binding Source={StaticResource operationTypeEnum}}" />
<ContentControl Grid.Column="2" Grid.Row="2" BorderThickness="3" BorderBrush="Black" Content="{Binding Path=SomeUserControl}" />
</Grid>
</Window>
XAML of the SomeUserControl:
<UserControl x:Class="LearnMVVM.SomeUserControl"
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:learnMvvm="clr-namespace:LearnMVVM"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<learnMvvm:SomeUserControlViewModel />
</UserControl.DataContext>
<DockPanel>
<TextBox DockPanel.Dock="Top" Margin="10" Text="{Binding Path=A, Mode=TwoWay, diag:PresentationTraceSources.TraceLevel=High}" />
<Label DockPanel.Dock="Top" Content="{Binding Path=Operation, diag:PresentationTraceSources.TraceLevel=High}" />
<TextBox DockPanel.Dock="Top" Margin="10" Text="{Binding Path=B, Mode=TwoWay, diag:PresentationTraceSources.TraceLevel=High}" />
<Button DockPanel.Dock="Top" Content="=" Margin="20" Command="{Binding CalculateOperationComamnd, Mode=TwoWay, diag:PresentationTraceSources.TraceLevel=High}" />
<Label DockPanel.Dock="Top" Margin="10" Content="{Binding Path=Result, diag:PresentationTraceSources.TraceLevel=High}" />
</DockPanel>
</UserControl>
ViewModel of the SomeCustomUserControl:
using System;
using System.ComponentModel;
using System.Windows.Input;
namespace LearnMVVM
{
public enum OperationType
{
Sum,
Sub,
Div,
Mul
}
public class SomeUserControlViewModel : INotifyPropertyChanged
{
public double A { get; set; }
public double B { get; set; }
//Команды
private ICommand calculateOperationCommand;
public ICommand CalculateOperationComamnd
{
get
{
return calculateOperationCommand;
}
set
{
if (calculateOperationCommand != value)
{
calculateOperationCommand = value;
OnPropertyChanged("CalculateOperationComamnd");
}
}
}
private OperationType operation;
public OperationType Operation
{
get
{
return operation;
}
set
{
if (operation != value)
{
operation = value;
switch (operation)
{
case OperationType.Sum:
CalculateOperationComamnd = new RelayCommand(arg => OperationSum());
break;
case OperationType.Sub:
CalculateOperationComamnd = new RelayCommand(arg => OperationSub());
break;
case OperationType.Div:
CalculateOperationComamnd = new RelayCommand(arg => OperationDiv());
break;
case OperationType.Mul:
CalculateOperationComamnd = new RelayCommand(arg => OperationMul());
break;
}
OnPropertyChanged("Operation");
}
}
}
private void OperationSum()
{
Result = A + B;
}
private void OperationSub()
{
Result = A - B;
}
private void OperationDiv()
{
Result = A/B;
}
private void OperationMul()
{
Result = A*B;
}
private double result;
public double Result
{
get { return result; }
set
{
if (Math.Abs(result - value) > 0.0001)
{
result = value;
OnPropertyChanged("Result");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Problem: the custom control does not change, when i'm changing selected item from the combo box and the "calculate button has no effect.
Actually all properties within of SomeCustomControlViewModel are updated as expected, but there is no effect in the main windows.
Have i missed something?
Operation is not a property of SomeUserControl. It is a property of SomeUserControl's viewmodel -- reachable as the control's DataContext. Try binding ComboBox.SelectedItem like so:
SelectedItem="{Binding Path=SomeUserControl.DataContext.Operation, Mode=TwoWay}"
The change is that I added DataContext to the path.
This is why you don't use viewmodels with custom controls, if you really want to use them as controls. You write a control class deriving from Control, and give it dependency properties. Operation should be a dependency property of a class derived from Control, not a notifying property on a viewmodel. Then you define UI for it by applying a ControlTemplate via a default Style.
What you've got here is really a child viewmodel. With that type of arrangement, ordinarily the parent viewmodel would provide an instance of the child viewmodel, and bind it to the child control itself. Then anybody who wanted to use a property of the child viewmodel would bind ChildVM.WhateverProperty.
I am new to WPF, just got thrown on a project. There is already an existing LoginWindow, and currently there is a forgot password button which opens a new dialog window. The manager wants that changed to use the same window, instead of opening a new one on top of it. I haven't seen a really solid example of how to accomplish this, as there seems to be a lot of different ways to do things in WPF. My initial though was maybe to use a Panel like a div and show and hide them in the same window like I DIV in HTML but not sure.
The current login someone already built looks like this:
<Controls:MetroWindow x:Class="Omega.LoginView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:properties="clr-namespace:Omega.Properties"
xmlns:tools="clr-namespace:Omega.modules.Features"
xmlns:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
Title="USER LOGIN" Icon="resources/icons/OmegaApp.ico"
TextOptions.TextFormattingMode="Display"
ShowInTaskbar="True" Topmost="False"
WindowStartupLocation="CenterScreen" ResizeMode="NoResize"
GlowBrush="{DynamicResource AccentColorBrush}" BorderThickness="1" EnableDWMDropShadow="True" WindowTransitionsEnabled="False" Closed="LoginWindow_Close" Height="446" Width="808" Visibility="Visible">
<Controls:MetroWindow.Background>
<ImageBrush ImageSource="resources\images\Omega-BGScreen.jpg"/>
</Controls:MetroWindow.Background>
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/resources/Resource_Dictionary.xaml"/>
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/FlatButton.xaml" />
<ResourceDictionary Source="/resources/Icons.xaml" />
</ResourceDictionary.MergedDictionaries>
<x:Array x:Key="WindowCommandsOverlayBehaviorArray"
Type="Controls:WindowCommandsOverlayBehavior">
<Controls:WindowCommandsOverlayBehavior>Always</Controls:WindowCommandsOverlayBehavior>
<Controls:WindowCommandsOverlayBehavior>Flyouts</Controls:WindowCommandsOverlayBehavior>
<Controls:WindowCommandsOverlayBehavior>HiddenTitleBar</Controls:WindowCommandsOverlayBehavior>
<Controls:WindowCommandsOverlayBehavior>Never</Controls:WindowCommandsOverlayBehavior>
</x:Array>
</ResourceDictionary>
</Window.Resources>
<Grid >
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Image Grid.ColumnSpan="3" Source="resources/images/Omega_Logo.png" HorizontalAlignment="Left" Height="100" Margin="58,48,0,0" VerticalAlignment="Top" Width="186" Grid.RowSpan="2"/>
<TextBox Name="UserID_Text" Grid.ColumnSpan="2" Grid.Column="2" HorizontalAlignment="Left" Height="30" Grid.Row="2" TextWrapping="Wrap" Text="Omega" VerticalAlignment="Top" Width="190" VerticalContentAlignment="Center" Margin="0,30,0,0" TabIndex="0" IsTabStop="True"/>
<Button Style="{StaticResource StandardButton}" Content="{x:Static properties:Resources.Login_Label}" IsDefault="True" HorizontalAlignment="Left" Grid.Row="3" VerticalAlignment="Top" Width="400" Grid.ColumnSpan="4" Height="30" Grid.Column="2" Click="Login_Button_Click" TabIndex="2" />
<TextBox HorizontalAlignment="Left" Height="23" Margin="0,33,0,0" Grid.Row="5" TextWrapping="Wrap" Text="v 2.0" VerticalAlignment="Top" Width="52" RenderTransformOrigin="0.392,0.346" BorderThickness="0" Foreground="LightGray" Focusable="False"/>
<PasswordBox Name="Pass_Text" Password="nopassword" Grid.Column="4" HorizontalAlignment="Left" Margin="10,30,0,0" Grid.Row="2" VerticalAlignment="Top" Height="30" Grid.ColumnSpan="2" Width="190" VerticalContentAlignment="Center" TabIndex="1"/>
<Label Content="User Login" Grid.Column="2" HorizontalAlignment="Left" Grid.Row="2" VerticalAlignment="Top" Width="100"/>
<Button Style="{StaticResource LinkButton}" Content="{x:Static properties:Resources.ForgotPass_label}" Grid.Column="2" HorizontalAlignment="Left" Grid.Row="3" VerticalAlignment="Top" Click="ForgotPassword_Click" Width="Auto" Grid.ColumnSpan="2" Margin="0,35,0,0" Focusable="False"/>
<TextBlock Grid.Column="3" HorizontalAlignment="Left" Margin="18,36,0,0" Grid.Row="3" TextWrapping="Wrap" Text=" | " VerticalAlignment="Top" Width="Auto" Height="17" RenderTransformOrigin="2,0.529"/>
<Button Style="{StaticResource LinkButton}" Content="{x:Static properties:Resources.Help_label}" Grid.Column="3" HorizontalAlignment="Left" Margin="36,36,0,0" Grid.Row="3" VerticalAlignment="Top" Width="34" Height="17" Click="Help_Click" RenderTransformOrigin="0.559,1.824" Focusable="False"/>
</Grid>
</Controls:MetroWindow>
I'm sorry this is such a crude example but I hope it shows you how you could solve your problem. Below is a simple example on how to swap two user controls using a button...
You have a shell view:
<Window x:Class="LogonApplication.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:LogonApplication"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type local:ChangePasswordViewModel}">
<local:ChangePasswordView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:EnterPasswordViewModel}">
<local:EnterPasswordView />
</DataTemplate>
</Window.Resources>
<DockPanel LastChildFill="False">
<ContentPresenter Content="{Binding ActiveView}"
DockPanel.Dock="Top"/>
<StackPanel DockPanel.Dock="Bottom">
<Button Content="Change View" Command="{Binding ChangeViewCommand}"/>
</StackPanel>
</DockPanel>
</Window>
Bound to a view model like this:
public class MainWindowViewModel : ViewModelBase
{
ViewModelBase _activeView;
public MainWindowViewModel()
{
_activeView = new EnterPasswordViewModel();
ChangeViewCommand = new ChangeViewCommand(this);
}
public ViewModelBase ActiveView
{
get { return _activeView; }
set
{
_activeView = value;
OnPropertyChanged("ActiveView");
}
}
public ICommand ChangeViewCommand { get; set; }
}
Then all you need to do is change the viewmodel being displayed by your content presenter. This could be achieved by wiring up a command to toggle your user controls/view models.
class ChangeViewCommand : ICommand
{
private MainWindowViewModel _mainWindowViewModel;
public event EventHandler CanExecuteChanged;
public ChangeViewCommand(MainWindowViewModel mainWindowViewModel)
{
_mainWindowViewModel = mainWindowViewModel;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
if (_mainWindowViewModel.ActiveView.GetType() == typeof(EnterPasswordViewModel))
{
_mainWindowViewModel.ActiveView = new ChangePasswordViewModel();
}
else
{
_mainWindowViewModel.ActiveView = new EnterPasswordViewModel();
}
}
}
Heres the base class for completeness:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
I havent shown the other two views and view models but the views are just WPF User controls and the view models are classes that implement viewmodelbase. Let me know if you need more information.