c# mvvm databind for multiple click events not working - c#

i have made an picross application in c# als my secund c# application.
i am trying to keep it mvvm. but have a little problem with this.
i have an array of rectangles en when someone clicks 1 of theses rectangles the status of this one needs to be changed(color change).
i have managed to do this in a non mvvm way but really want to make is mvvm.
so the code.
first the xaml code.
<Window x:Class="View.CreatePuzzle"
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:View"
xmlns:controls="clr-namespace:View.Controls"
mc:Ignorable="d"
Title="CreatePuzzle" Height="450" Width="800">
<Window.Resources>
<VisualBrush x:Key="myBrush">
<VisualBrush.Visual>
<Grid>
<Rectangle Fill="Red"/>
<Image Source="Images/backgroundmain.jpg"/>
</Grid>
</VisualBrush.Visual>
</VisualBrush>
</Window.Resources>
<Grid Background="{StaticResource myBrush}" RenderTransformOrigin="0.508,0.819" Height="450" Margin="10,0,10,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="600"/>
</Grid.ColumnDefinitions>
<StackPanel>
<TextBlock Grid.Column="0" Text="your name" />
<TextBox Grid.Column="0" Text="{Binding Author}" />
<TextBlock Grid.Column="0" Text="width" />
<controls:numberinput x:Name="widthfield" Grid.Column="1" Value="{Binding puzzlewidth}">
</controls:numberinput>
<TextBlock Grid.Column="0" Text="height" />
<controls:numberinput x:Name="heightfield" Grid.Column="0" Value="{Binding puzzleheight}">
</controls:numberinput>
<Button Grid.Column="0" Height="25" Content="generate puzzle"
Command="{Binding Path=generatefield}"/>
</StackPanel>
<controls:PiCrossControl HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="1" x:Name="picrossControl" ColumnConstraints="{Binding ColumnConstraints}" RowConstraints="{Binding RowRonstraints }" Grid="{Binding Grid}" >
<controls:PiCrossControl.SquareTemplate>
<DataTemplate >
<Rectangle Width="32" Height="32" Stroke="Black" MouseRightButtonDown="Rectangle_MouseRightButtonDown" MouseWheel="Rectangle_MouseWheel">
<Rectangle.InputBindings>
<MouseBinding Gesture="LeftClick" Command="{Binding Path=Rectangle_MouseLeftButtonDown}"/>
</Rectangle.InputBindings>
<Rectangle.Fill>
<Binding Path="Contents.Value" UpdateSourceTrigger="PropertyChanged">
<Binding.Converter>
<local:SquareConverter Empty="White" Filled="Black" Unknown="green" />
</Binding.Converter>
</Binding>
</Rectangle.Fill>
</Rectangle>
</DataTemplate>
</controls:PiCrossControl.SquareTemplate>
</controls:PiCrossControl>
<Button Grid.ColumnSpan="2" Height="50" Content="create puzzle"
Command="{Binding Path=createpuzzle}" Margin="0,340,0,0"/>
</Grid>
here is one of the methodes is want to take out of the xaml.cs
private void Rectangle_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
Rectangle rectangle = (Rectangle)sender;
IPlayablePuzzleSquare square = (IPlayablePuzzleSquare)rectangle.DataContext;
square.Contents.Value = Square.EMPTY;
}
and the view model methode i wanna use to replace it with:
public void Rectangle_MouseLeftButtonDown(object sender )
{
Debug.WriteLine("leftmousevent in vm");
}
so when i run this code i get te following error for every rectangle :
System.Windows.Data Error: 40 : BindingExpression path error: 'Rectangle_MouseLeftButtonDown' property not found on 'object' ''PlayablePuzzleSquare' (HashCode=36468595)'. BindingExpression:Path=Rectangle_MouseLeftButtonDown; DataItem='PlayablePuzzleSquare' (HashCode=36468595); target element is 'MouseBinding' (HashCode=10961125); target property is 'Command' (type 'ICommand')
i hope someone can help me.
note i am pretty new to c# but have more java experience.
thanks in advance
jef uytterhoeven

As you said; Rectangle_MouseLeftButtonDown is an event handler.
Without seeing your view-model it's hard to fix this, but, you should use the same structure as you do in the Command binding for your button:
Command="{Binding Path=generatefield}"
So, look for generatefield in your view model and duplicate and adjust it to replace the event handler.

Since you do not wish code behind, you have to remove your Mouse left button click event handler from xaml.cs.
Instead you can make use of System.Windows.Interactivity.dll .
Add this reference to your project first. Then add following code in xaml . (Example)
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown" >
<i:InvokeCommandAction Command="{Binding MouseLeftButtonDownClick}" >
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</Rectangle>
As you can see in the example, my event is bound to command,which is in the View model . If you are not familiarize with EventToCommand binding , you can learn more from following link
WPF Binding UI events to commands in ViewModel
After this, you have to implement ICommand in your viewmodel . (Example)
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ICommand MouseLeftButtonDownClick { get; }
public ViewModel()
{
MouseLeftButtonDownClick = new RelayCommand(OnMouseLeftButtonClick);
}
private void OnMouseLeftButtonClick(object obj)
{
//Your logic on left button click
}
public void OnPropertyChanged(string PropName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropName));
}
}
I have used RelayCommand class in my viewmodel, which is nothing but ICommand implementation . If you are not familiarize with ICommand , you can learn more from following link
ICommand MVVM implementation

Related

Proper way of specifying DataContext (WPF)

I just started working with WPF. In my new application I implemented notify icon with context menu first. Next I started building MVVM framework and found that the new changes impact the code already implemented.
I am using NotifyIcon from Hardcodet. My initial version was something like this:
<Window x:Class="ScanManager.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpf="http://schemas.microsoft.com/wpf/2008/toolkit"
xmlns:tb="http://www.hardcodet.net/taskbar"
xmlns:commands="clr-namespace:ScanManager.Commands"
Title="Scan" Height="542" Width="821">
<Grid Visibility="Visible" Loaded="form_Loaded">
...
<tb:TaskbarIcon HorizontalAlignment="Left" Margin="357,537,0,0" Name="mainTaskbarIcon" VerticalAlignment="Top" IconSource="/Icons/TestIcon.ico" IsHitTestVisible="True" ToolTipText="Test Test" >
<tb:TaskbarIcon.ContextMenu>
<ContextMenu>
<MenuItem Header="_Show" Command="{commands:ShowMainWindowCommand}" CommandParameter="{Binding}" />
<MenuItem Header="_Hide" Command="{commands:HideMainWindowCommand}" CommandParameter="{Binding}" />
</ContextMenu>
</tb:TaskbarIcon.ContextMenu>
</tb:TaskbarIcon>
<Button Name="hideButton" Content="Hide window" Height="23" HorizontalAlignment="Right" Margin="0,408,50,0" VerticalAlignment="Top" Width="92" IsEnabled="True" Click="hideButton_Click" />
</Grid>
</Window>
Next I started incorporating MVVM pattern based on article The World's Simplest C# WPF MVVM Example. The example project adds DataContext pointing to a ViewModel class.
<Window.DataContext>
<ViewModels:Presenter/>
</Window.DataContext>
This change affected the way the notification icon works. In a nutshell, the overriding methods ICommand.CanExecute(object parameter) and ICommand.Execute(object parameter) of the ShowMainWindowCommand and HideMainWindowCommand objects started receiving object Presenter defined in Window.DataContext instead of original Hardcodet.Wpf.TaskbarNotification.TaskbarIcon. And I am guessing this is because the added DataContext affects the {Binding} value of the CommandParameter.
The Execute method expects the parameter to be TaskbarIcon in order to identify the parent Window object, which then can be set shown or hidden.
The way I was trying to address it I moved all elements but the TaskbarIcon from Window to a UserControl, under a Grid and applied DataContext to the Grid
<UserControl x:Class="ScanManager.Views.SControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
...
d:DataContext="{d:DesignInstance ViewModels:Presenter}">
<Grid Visibility="Visible">
<Grid.DataContext>
<ViewModels:Presenter/>
</Grid.DataContext>
<Button Name="hideButton" Command="{Binding Path=HideMainWindowCommand}" CommandParameter="{Binding}" Content="Hide window" Height="23" HorizontalAlignment="Right" Margin="0,408,50,0" VerticalAlignment="Top" Width="92" IsEnabled="True" Click="hideButton_Click" />
...
</Grid>
</UserControl>
It addressed the issue with notify icon, but I am wondering if this is right way of resolving the situation. I thought the other way could be to set CommandParameter in MenuItem in the original version after DataContext was added, to proper value, however I am having hard time figuring this out.
As the next step, I am trying to cast DataContext of the UserControl object to INotifyPropertyChanged in order to subscribe to PropertyChanged event, however the DataContext property comes in as null, presumable because it was set only to Grid and not to the UserControl:
INotifyPropertyChanged viewModel = (INotifyPropertyChanged)this.DataContext;
Any guidance on putting these pieces together properly would be much appreciated.
Edit
Access Denied, these options are helpful for the Button element.
What if I would like to come back to the initial version at the top, the MenuItem element uses Command="{commands:ShowMainWindowCommand}" and CommandParameter="{Binding}". If I add Window.DataContext, is there a change that can be done to the MenuItem's Command/CommandParameter attributes in order to reference what they referred before (I assume, the parent element)? I tried CommandParameter="{Binding Path=mainTaskbarIcon}" and it did not work meaning, like before, the Execute/CanExecute receive null.
<Window x:Class="ScanManager.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpf="http://schemas.microsoft.com/wpf/2008/toolkit"
xmlns:tb="http://www.hardcodet.net/taskbar"
xmlns:commands="clr-namespace:ScanManager.Commands"
Title="Scan" Height="542" Width="821">
<Window.DataContext>
<ViewModels:Presenter/>
</Window.DataContext>
<Grid Visibility="Visible" Loaded="form_Loaded">
...
<tb:TaskbarIcon HorizontalAlignment="Left" Margin="357,537,0,0" Name="mainTaskbarIcon" VerticalAlignment="Top" IconSource="/Icons/TestIcon.ico" IsHitTestVisible="True" ToolTipText="Test Test" >
<tb:TaskbarIcon.ContextMenu>
<ContextMenu>
<MenuItem Header="_Show" Command="{commands:ShowMainWindowCommand}" CommandParameter="{Binding Path=mainTaskbarIcon}" />
<MenuItem Header="_Hide" Command="{commands:HideMainWindowCommand}" CommandParameter="{Binding Path=mainTaskbarIcon}" />
</ContextMenu>
</tb:TaskbarIcon.ContextMenu>
</tb:TaskbarIcon>
...
</Grid>
</Window>
When you set datacontext it spreads to the inner controls as well and yes it affects Binding context. There is no need to create UserControl since it does not prevent context from spreading. In order to prevent it change datacontext of the control or specify binding source. For example, if you want to change context of the button.
Approach with DataContext override:
<Grid Visibility="Visible">
<Grid.Resources>
<ViewModels:Presenter x:Key="buttonContext"/>
</Grid.Resources>
<Button DataContext="{StaticResource buttonContext}" Name="hideButton" Command="{Binding Path=HideMainWindowCommand}" CommandParameter="{Binding}" Content="Hide window" Height="23" HorizontalAlignment="Right" Margin="0,408,50,0" VerticalAlignment="Top" Width="92" IsEnabled="True" Click="hideButton_Click"/>
Approach with specifying source:
<Grid.Resources>
<ViewModels:Presenter x:Key="buttonContext"/>
</Grid.Resources>
<Button Name="hideButton" Command="{Binding Source={StaticResource buttonContext}, Path=HideMainWindowCommand}" Content="Hide window" Height="23" HorizontalAlignment="Right" Margin="0,408,50,0" VerticalAlignment="Top" Width="92" IsEnabled="True" Click="hideButton_Click"/>
Or you can also have ButtonContext property in your root viewModel and resolve it this way:
<Button DataContext="{Binding ButtonContext}" Name="hideButton" Command="{Binding Path=HideMainWindowCommand}" CommandParameter="{Binding}" Content="Hide window" Height="23" HorizontalAlignment="Right" Margin="0,408,50,0" VerticalAlignment="Top" Width="92" IsEnabled="True" Click="hideButton_Click"/>
How to subscribe to DataContextChanged event:
public MainWindow()
{
InitializeComponent();
DataContextChanged += MainWindow_DataContextChanged;
Handle event:
private void MainWindow_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue != null && e.OldValue is INotifyPropertyChanged)
{
((INotifyPropertyChanged)e.OldValue).PropertyChanged -= MainWindow_PropertyChanged;
}
if (e.NewValue != null && e.NewValue is INotifyPropertyChanged)
{
((INotifyPropertyChanged)e.NewValue).PropertyChanged += MainWindow_PropertyChanged;
}
}
private void MainWindow_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
...
}
You don't have to adhere to Commanding. Change your menu items to use a click event instead, and they can call command operation from the View's codebehind.
Add Click="{Your click event name}"
F12 on the click event to create/go to the event.
As an aside here is a way to bind a VM to a data contect without doing it in the XAML.
Xaml: ViewModel Main Page Instantiation and Loading Strategy for Easier Binding

DataItem=null on binding, can't find out why?

I am trying to reproduce what is suggested in Sheridan's answer to this question to navigate trough my views when using WPF with the MVVM pattern. Unfortunately, I am getting a binding error when I do so. Here is the exact error:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='JollyFinance.ViewModels.MainViewModel', AncestorLevel='1''. BindingExpression:Path=DataContext.DisplayTest; DataItem=null; target element is 'Button' (Name=''); target property is 'Command' (type 'ICommand')
When I look into my xaml code in LoginView.xaml, I noticed that Visual Studio is telling me that it cannot find DataContext.DisplayText in context of type MainViewModel. I have tried removing DataContext. and just keeping DisplayText instead, but to no avail.
Unless Sheridan's answer has an error, I am most definitely missing something here. What should I do for it to work?
MainWindow.xaml:
<Window x:Class="JollyFinance.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:JollyFinance.ViewModels"
xmlns:views="clr-namespace:JollyFinance.Views"
Title="JollyFinance!" Height="720" Width="1280">
<Window.Resources>
<!-- Different pages -->
<DataTemplate DataType="{x:Type vm:LoginViewModel}">
<views:LoginView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:TestViewModel}">
<views:Test/>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
<Grid>
<ContentControl Content="{Binding CurrentViewModel}"/>
</Grid>
</Window>
MainViewModel.cs:
public class MainViewModel : BindableObject
{
private ViewModelNavigationBase _currentViewModel;
public MainViewModel()
{
CurrentViewModel = new LoginViewModel();
}
public ICommand DisplayTest
{
get
{
// This is added just to see if the ICommand is actually called when I press the
// Create New User button
Window popup = new Window();
popup.ShowDialog();
// View model that doesn't contain anything for now
return new RelayCommand(action => CurrentViewModel = new TestViewModel());
}
}
public ViewModelNavigationBase CurrentViewModel
{
get { return _currentViewModel; }
set
{
if (_currentViewModel != value)
{
_currentViewModel = value;
RaisePropertyChanged("CurrentViewModel");
}
}
}
}
LoginView.xaml:
<UserControl x:Class="JollyFinance.Views.LoginView"
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:vm="clr-namespace:JollyFinance.ViewModels"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<vm:LoginViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Username: " Grid.Column="1" Grid.Row="1" Margin="5"/>
<TextBox Text="{Binding Path=Username}" Grid.Column="2" Grid.Row="1" Grid.ColumnSpan="2" Margin="5"/>
<TextBlock Text="Password: " Grid.Column="1" Grid.Row="2" Margin="5"/>
<PasswordBox x:Name="PasswordBox" PasswordChar="*" Grid.Column="2" Grid.ColumnSpan="2" Grid.Row="2" Margin="5"/>
<Button Content="Log In" Grid.Column="2" Grid.Row="3" Margin="5" Padding="5" Command="{Binding LoginCommand}"/>
<Button Content="Create new user" Grid.Column="3" Grid.Row="3" Margin="5" Padding="5"
Command="{Binding DataContext.DisplayTest, RelativeSource={RelativeSource AncestorType={x:Type vm:MainViewModel}},
Mode=OneWay}"/>
</Grid>
</UserControl>
LoginViewModel.cs:
public class LoginViewModel : ViewModelNavigationBase
{
public LoginViewModel()
{
LoginCommand = new RelayCommand(Login);
}
private void Login(object param)
{
// Just there to make sure the ICommand is actually called when I press the
// Login button
Window popup = new Window();
popup.ShowDialog();
}
public String Username { get; set; }
public String Password { get; set; }
public ICommand LoginCommand { get; set; }
}
ViewModelNavigationBase is just a class that implements the INotifyPropertyChanged interface, and Test.xaml and TestViewModel.cs are just a dummy viewmodel/view for test purposes.
In my answer, I stated that you should declare your view model DataTemplates in App.xaml so that every view will have access to them. Putting them in the MainWindow class is your first problem.
Another mistake is your Binding Path for your ICommand. If you want to access something from the view model that is set as the Window.DataContext, then you should not use a RelativeSource Binding . Try this instead:
<Button Content="Create new user" Grid.Column="3" Grid.Row="3" Margin="5" Padding="5"
Command="{Binding DataContext.DisplayTest}, Mode=OneWay}" />
Also remember that for whatever reason, you chose not to make your MainViewModel class extend the ViewModelNavigationBase class... that could also cause you problems.
Anyway, if that doesn't sort out your problems, just let me know. Also, if you want to notify a user at anytime on Stack Overflow, just put an # symbol in front of their name and they will receive a notification. You could have asked me this question directly if you had done that.
MainViewModel is not a direct ancestor in the visual or logical tree, which is why RelativeSource={RelativeSource AncestorType={x:Type vm:MainViewModel}} cannot find it.
How do you fix it? First, please don't try and reach through various UI components like this to trigger commands. Just because you saw it somewhere on the internet doesn't mean it is a desirable design choice. Doing this means the LoginView has a deep understanding of other views and view models - which is bad. If you are going to do that then you might as well code everything as one single UI class with a single viewmodel that is really just a massive code behind class.
A better (but still not optimal) approach is to have the MainView (or viewmodel) spawn the LoginView. As it holds the reference to the view, it is also responsible for disposing of it. So the LoginView can be shown to collect credentials, then the main view can dispose if it signals that the credentials are validated successfully. Or it can just collect credentials and leave it up to the MainView/viewmodel to validate them (which can be done by the MainView/viewmodel triggering a background call to check the credentials against a store).
A simple (crude) rule of thumb is: a parent view can know about a child view, but in general the reverse should not happen. MVVM is about decoupling and segregating functionality, but instead you are tightly coupling them. Of course all this gets a whole lot more complex than what I've illustrated, but you can still do some of this while keeping it practical and not over-engineering.
So, TLDR;:
the LoginView (or its viewmodel) should implement its own command to deal with the button click
don't reach deep through the entrails of another view to trigger functionality
strive for SRP and de-coupled code/views
when using ancestor binding, look for something that's in the visual/logical tree
Define MainViewModel in App scope as a static resource.
<App.Resources>
<MainViewModel x:Key="MainViewModel" />
</App.Resources>
Then you will be able to bind MainViewModel commands from any view.
<Button Command="{Binding Source={StaticResource MainViewModel}, Path=DisplayTest}" />
EDIT
Or try this code:
<Button Command="{Binding DisplayTest, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window), Path=DataContext}}"/>

Radio Button Binding MVVM Application

I have a radiobutton.xaml file that has 4 radio buttons and a button
I showed radio button on the mainwindow by this code
<ContentControl Content="{StaticResource RB}" Height="326" x:Name="select" />
Now I need to implement binding for the radio button
I can't bind the radio buttons and the button to a view model. need to open new windows on behalf of the selected radio button on click of the button.
having difficulty in making V-M for radio button. don't know exactly where to put the binding code ...
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:src="clr-namespace:DiagramDesigner">
<GroupBox x:Key="RB" Header="Select The Architecture Modeling Style" Height="400" >
<StackPanel>
<TextBlock Text="Custom Style Architecture Modeling:" FontSize="20"
Margin="30 30 40 10" HorizontalAlignment="Center" />
<RadioButton Content="Custome Architecture Modeling" Margin="50 0 10 10"
GroupName="Standard" />
<TextBlock Text="Standard Style Architecture Modeling:" FontSize="20"
Margin="30 0 40 10" HorizontalAlignment="Center" />
<RadioButton Content="3-Tier Architecture Modeling" Margin="50 0 10 0"
GroupName="Standard" />
<RadioButton Content="Client-Server Architecture Modeling"
Margin="50 0 10 0" GroupName="Standard" />
<RadioButton Content="Pipeline and Filter Architecture Modeling"
Margin="50 0 10 0" GroupName="Standard" />
<Button Margin="100 20 100 0" Width="200" HorizontalContentAlignment="Center">
<Button.Content>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="4*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="1" Text="Let's Go Draw It..." VerticalAlignment="Bottom" HorizontalAlignment="Center"/>
</Grid>
</Button.Content>
</Button>
</StackPanel>
</GroupBox>
</ResourceDictionary>
need to bind it as MVVM
I'd recommend you to use the ListBox method instead of the one you mentioned. You may find it here:
http://www.codeproject.com/Tips/807025/WPF-MVVM-Binding-for-Multiple-Radio-Buttons
If you'd like to keep the "abstract" groups (custom and standard style architecture modeling), then one of the solution that comes to my mind now is to implement a TextBox in the ControlTemplate and bind it to a property on the view model.
<ListBox ItemsSource="{Binding RadioCollection}" SelectedItem="{Binding SelectedRadio}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<TextBlock Text="{Binding AbstractGroupHeader}" />
<RadioButton
Content="{Binding Header}" ToolTip="{Binding ToolTip}"
IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsSelected}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Margin" Value="5"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
The view model basically is responsible for the view's state (e.g. an animation on the view isn't definied in the view model, but the view model may release, as in start it). VM is a class that you define and it must implement INotifyPropertyChanged interface found in namespace System.ComponentModel in case you want the view to be notified when you change property's value in code. Keep in mind, that the property must have a public access modifier, in order for the binding to work. If you want to follow this method, then read the article located under the link I gave. However, if you want a simpler solution, which will work with your code, then you have to bind IsChecked dependency property of each of the radio buttons to appropriate properties on the view model, like this:
<RadioButton Content="Pipeline and Filter Architecture Modeling"
Margin="50 0 10 0" GroupName="Standard" IsChecked="{Binding IsPipelinedModeling}"/>
And the VM in this case would look like this:
public class SettingsViewModel : INotifyPropertyChanged
{
bool _isPipelinedModeling;
public bool IsPipelinedModeling
{
get { return _isPipelinedModeling; }
set
{
if (_isPipelinedModeling == value)
return;
_isPipelinedModeling = value;
RaisePropertyChanged();
}
}
#region INotifyPropertyChanged implementation
public void RaisePropertyChanged([CallerMemberName]string propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion INotifyPropertyChanged implementation
}
To bind the view model to the view you can use either "view first" or "view model first" approach. I'm using view first. In the constructor of the window, user control or whatever you're using, add the following code:
this.Loaded += (s, e) =>
{
this.DataContext = new SettingsViewModel();
};
The code creates a new view model object and sets it as the window's DataContext.
Binding to a button is a little bit different though, because you have to declare a command. It is a class of your own, implementing ICommand interface:
ICommand _drawModelingArchitectureCommand;
public ICommand DrawModelingArchitectureCommand
{
get
{
return _drawModelingArchitectureCommand ?? (_drawModelingArchitectureCommand = new DrawTheModelingArchitectureCommand());
}
}
public class DrawTheModelingArchitectureCommand : ICommand
{
public bool CanExecute(object parameter)
{
// the code that decides whether the button will be enabled or not
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
// the code that is executed when the button is pressed
}
}
And finally the XAML for the button:
<Button Grid.Row="1" Content="Let's Go Draw It..." VerticalAlignment="Bottom" HorizontalAlignment="Center" Command="{Binding DrawTheModelingArchitecture}"/>

How to add controls dynamically to a UserControl through user's XAML?

I want to create a user control that contains a TextBlock and a StackPanel that will allow the user to add his/her own controls to the user control dynamically in XAML.
Here is the sample XAML for my UserControl:
<UserControl x:Class="A1UserControlLibrary.UserControlStackPanel"
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="200" d:DesignWidth="300">
<StackPanel>
<TextBlock Text="I want the user to be able to add any number of controls to the StackPanel below this TextBlock."
FontFamily="Arial" FontSize="12" FontWeight="DemiBold" Margin="5,10,5,10" TextWrapping="Wrap"/>
<StackPanel>
<!-- I want the user to be able to add any number of controls here -->
</StackPanel>
</StackPanel>
</UserControl>
I would like the user to be able to embed this user control in their XAML and add their own controls to the stack panel of the user control:
<uc:A1UserControl_StackPanel x:Name="MyUserControl_Test" Margin="10" Height="100">
<Button Name="MyButton1" Content="Click" Height="30" Width="50"/>
<Button Name="MyButton2" Content="Click" Height="30" Width="50"/>
<Button Name="MyButton3" Content="Click" Height="30" Width="50"/>
</uc:A1UserControl_StackPanel>
Doing this using the above XAML does not work. Any ideas?
You can do that, although not quite like your example. You need two things. The first is to declare a DependencyProperty of type UIElement, of which all controls extend:
public static DependencyProperty InnerContentProperty = DependencyProperty.Register("InnerContent", typeof(UIElement), typeof(YourControl));
public UIElement InnerContent
{
get { return (UIElement)GetValue(InnerContentProperty); }
set { SetValue(InnerContentProperty, value); }
}
The second is to declare a ContentControl in the XAML where you want the content to appear:
<StackPanel>
<TextBlock Text="I want the user to be able to add any number of controls to the StackPanel below this TextBlock."
FontFamily="Arial" FontSize="12" FontWeight="DemiBold" Margin="5,10,5,10" TextWrapping="Wrap"/>
<StackPanel>
<ContentControl Content="{Binding InnerContent, RelativeSource={RelativeSource AncestorType={x:Type YourXmlNamspacePrefix:ContentView}}}" />
</StackPanel>
</StackPanel>
In my opinion, if you use StackPanels, you could find that your content does not get displayed correctly... I'd advise you to use Grids for layout purposes for all but the simplest layout tasks.
Now the one difference to your example is in how you would use your control. The InnerContent property is of type UIElement, which means that it can hold one UIElement. This means that you need to use a container element to display more than one item, but it has the same end result:
<YourXmlNamspacePrefix:YourControl>
<YourXmlNamspacePrefix:YourControl.InnerContent>
<StackPanel x:Name="MyUserControl_Test" Margin="10" Height="100">
<Button Content="Click" Height="30" Width="50"/>
<Button Content="Click" Height="30" Width="50"/>
<Button Content="Click" Height="30" Width="50"/>
</StackPanel>
</YourXmlNamspacePrefix:YourControl.InnerContent>
</YourXmlNamspacePrefix:YourControl>
And the result:
UPDATE >>>
For the record, I know exactly what you want to do. You, it seems, do not understand what I am saying, so I'll try to explain it one last time for you. Add a Button with the Tag property set as I've already shown you:
<Button Tag="MyButton1" Content="Click" Click="ButtonClick" />
Now add a Click handler:
private void ButtonClick(object sender, RoutedEventArgs e)
{
Button button = (Button)sender;
if (button.Tag = "MyButton1") DoSomething();
}
That's all there is to it.

MVVM - Access command from different class in XAML

In my project I have a TitleView and GameView. When the program launches, TitleView is displayed. The user clicks a button and GameView is displayed. I am using MVVM Light which includes MainViewModel which has commands to switch to the desired views:
From MainViewModel.cs
GameViewCommand = new RelayCommand(() => ExecuteGameViewCommand());
private void ExecuteGameViewCommand()
{
CurrentViewModel = MainViewModel._gameViewModel;
}
In TitleView.xaml, I need to access this command and I don't know how. I am very much a novice when it comes to XAML.
From TitleView.xaml
<UserControl x:Class="AoW.Views.TitleView"
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:vm="clr-namespace:AoW.ViewModels"
mc:Ignorable="d"
d:DesignWidth="1020"
d:DesignHeight="740"
Width="1020"
Height="740">
<Button Content="New Game"
//This needs to bind to GameViewCommand in MainViewModel.cs
Command="{Binding GameViewCommand}"
Grid.Column="1"
Grid.Row="1"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
If I put the following line into TitleView.xaml...
DataContext="{Binding Main, Source={StaticResource Locator}}"
... I can access GameViewCommand, but I can't access any local commands.
What can I do to gain access to GameViewCommand while maintaining control of local commands?
If providing additional code would be helpful, please let me know.
I've fixed the problem. All I had to was this:
<Button Content="New Game"
Command="{Binding GameViewCommand}"
DataContext="{Binding Main, Source={StaticResource Locator}}"
Grid.Column="1"
Grid.Row="1"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
<Button Content="Say Hello"
Command="{Binding SayHelloCommand}"
Grid.Column="2"
Grid.Row="2"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
Turns out it was a very easy fix.

Categories