ICommand not working in Silverlight User control - wpf

I have a UserControlBase with a grid. The grid contains a column with an Action.
<sdk:DataGridTemplateColumn Header="Action">
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<Button Style="{StaticResource DataGridButton}" Command="{Binding Source={StaticResource NewsViewModel}, Path=ModifyNewsCommand}" Content="Modify" />
</StackPanel>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
My problem is with my Command. It is throwing me Technical Error which is my first problem, I don't know how to make this application throw me the real error message.
In the code behind of my user control I registered the events:
protected void RegisterMessages()
{
Messenger.Default.Register<string>(this, "NewNewsBtn_Click", NewNewsBtn_Click);
Messenger.Default.Register<string>(this, "ModifyNewsBtn_Click", ModifyNewsBtn_Click);
}
And in my constructor:
public NewsWindow(int underlyingId)
{
InitializeComponent();
this.RegisterMessages();
viewModel = new NewsViewModel(underlyingId);
ucNewsPanel.DataContext = viewModel;
}
My view model (NewsViewModel)
public ICommand ModifyNewsCommand
{
get
{
return new RelayCommand<string>(e =>
{
Messenger.Default.Send(string.Empty, "ModifyNewsBtn_Click");
});
}
}
What's weird here is that my NewNewsBtn is working, while my ModifyNewsBtn is not.
This button is outside the grid so it might make a difference on why it is working.
<Button x:Name="NewNewsBtn" MaxHeight="50" MaxWidth="100" Command="{Binding Path=NewNewsCommand}" Content="Add New" />

Your DataGrid will be bound to some collection, having a row per each item. Now the item is the DataContext for a row. What you need to do is bind your "Modify"-button to the parent DataContext. If you are using silverlight5 you can use an AncestorBinding:
<Button
Content="Modify"
Command="{Binding
Path=DataContext.ModifyNewsCommand,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>

Your syntax looks okay:
<Button Style="{StaticResource DataGridButton}" Content="Modify"
Command="{Binding Source={StaticResource NewsViewModel}, Path=ModifyNewsCommand}"/>
But you set your viewModel in code. Am I right that you create StaticResource in XAML? If it is, then just delete setting DataContext from code-behind cause the following row Command="{Binding Source={StaticResource NewsViewModel}, Path=ModifyNewsCommand}" will see in the another instance of your viewModel. (cause you've create two instances of NewsViewModel as a StaticResource and in code-behind)
Update:
I needed the DataContext in code behind as I am passing a parameter to my view model. Any way I can do that if I removed it from code behind?
Then you should delete StaticResource from Binding of Command:
<Button Style="{StaticResource DataGridButton}" Command="{Binding ModifyNewsCommand}"
Content="Modify"/>
Cause you are referencing to another instance of NewsViewModel.

Related

Bind IsEnable property of button located inside data grid to variable

I want to bind IsEnable property of button to variable. Button is added inside data grid. Data Grid is located inside user control.
Below code is not working.
<DataGridTemplateColumn Header="Start" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button x:Name="Start_Button"
DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type UserControl}},Path=DataContext}"
IsEnabled="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DataGrid}},Path=IsStartButtonEnable}"
Command="{Binding StartCommand}">START
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Model contains-(ServerModel.cs)
private bool _isStartButtonEnable = false;
public bool IsStartButtonEnable
{
get
{
return _isStartButtonEnable;
}
set
{
_isStartButtonEnable = value;
this.OnPropertyChanged("IsStartButtonEnable");
}
}
View Model(ServerViewModel.cs) Contains -
public static ObservableCollection<ServerModel> DataGridServerList { get; set; }
UserControl (ServerUserControl.cs) contains -
this.DataContext = new ServerViewModel();
Requirements - UserControl contains 3 buttons-Create, Start and Stop. User clicks on Create Button. If Create commnd gets success, then Start button gets enabled.Then user will click on Start button. If start commnd gets executed successfully, then Stop button gets enabled and so on.
<DataGridTemplateColumn Header="Start" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button x:Name="Start_Button"
IsEnabled="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type UserControl}},Path=DataContext.IsStartButtonEnable}"
Command="{Binding StartCommand}">START
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
I am using the DataContext of the UserControl directly and not that of the DataGrid.
In your case, indeed, the DataContext of the Button will be set to each item in the DataGrid. In order to access the property on your UserControl you need this change. Further more, you don't need to set the DataContext again so i removed that line.
You have two possibilities in my opinion. Either set the flag in the ViewModel and will be accessible by all Buttons through the Relative Source syntax or if you have it in the Model, you will have a flag for each row in the DataGrid. In either case you have the response:
If it is in the Model, set the DataGrid ItemsSource to your ObservableCollection and use
IsEnabled = {Binding IsStartButtonEnable }
2.If it is in the ViewModel, use
IsEnabled="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type UserControl}},Path=DataContext.IsStartButtonEnable}"
I don't see other option but if you want it available for all Buttons in the DataGrid you have to set it in the ViewModel.

ContextMenu click of DataGrid in view model

I have use the following code snippet to get the ContextMenu click of DataGrid in view model
Code Snippet[XAML]
<DataGrid AutoGenerateColumns="True" ItemsSource="{Binding GDCSource}" x:Name="dataGrid">
<DataGrid.ContextMenu>
<ContextMenu >
<ContextMenu.Items>
<MenuItem Header="Export To Excel" Command="{Binding ExportCommand}" CommandParameter="{Binding ElementName=dataGrid}"/>
</ContextMenu.Items>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
Code Snippet[C#]
private BaseCommand exportcommand;
public BaseCommand ExportCommand
{
get
{
if (exportcommand == null)
exportcommand = new BaseCommand(ExportCommandExcecuted);
return exportcommand;
}
}
public void ExportCommandExcecuted(object param)
{
var grid = param as GridDataControl;
}
But I cannot get the Command parameter as DataGrid. Can you please look into this and provide suggestion to achieve this dataGrid in ViewModel. Thanks in advance
Am not sure if I should even tell you how to do this since your breaking MVVM if you are essentially doing this and I cannot figure out a reason for you wanting this functionality.
If you're not using MVVM, then you could try having your MenuItem such as
<MenuItem Command="{Binding ExportCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}, Path=PlacementTarget}"
Header="Export To Excel" />
If you're using MVVM,
Just don't reference UI elements in the VM. You got the ItemSource collection in the VM. Raise a plain simple ICommand and all the data you need is available without you getting the DataGrid to the VM.

WPF MVVM DataTemplate: Inject Template ViewModel with Data from parent Viewmodel

I have an ObservableCollection in my parent ViewModel which I want to display in the parent View. So I've defined a child View and a child ViewModel handling button clicks.
How do I get each item from the ObservableCollection into the according child ViewModel without loosing my already setup RelayCommands for button click handling?
In my parent View Code-Behind the only thing I do is setting the DataContext to the appropiate ViewModel:
DataContext = new ParentViewModel();
In my parent View XAML I defined a ListBox to display a DataTemplate of my child View:
<ListBox
ItemsSource="{Binding Path=Items}">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type vm:ChildViewModel}">
<views:ChildView Width="auto" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Now in my ChildView I got several TextBlocks displaying the Binding Data and Buttons which should execute a file in a path specified within the ObservableCollection:
<TextBlock
Text="{Binding Path=Title}" />
...
<Button
Content="Start exe"
Tag="{Binding Path=ExePath}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding Path=OnButtonClicked}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
The Child ViewModel holds RelayCommands to handle the Button click event:
private RelayCommand onButtonClicked;
public ICommand OnButtonClicked
{
get
{
return onButtonClicked ??
(onButtonClicked =
new RelayCommand(ObeyOnButtonClicked, CanObeyOnButtonClicked));
}
}
private void ObeyOnButtonClicked()
{
... //Path conversion
Process.Start(pathToExe);
}
private bool CanObeyOnButtonClicked()
{
return true;
}
Now, within my child View's Code-Behind, when I add
DataContext = new SampleItemViewModel();
to the Constructor, the Button click is handled but the TextBoxes are all empty.
When i remove this line, the TextBoxes are filled correctly, but the Button Clicks are not handled.
How do I get both features working?
EDIT:
ParentViewModel:
private ObservableCollection<Item> items;
public ObservableCollection<Item> Items
{
get { return items; }
set
{
items= value;
OnPropertyChanged("Items");
}
}
... //Filling the Collection in Constructor
ChildViewModel contains just the above mentioned button click handlers.
EDIT:
I tried several things now but I don't know how to bind the Command from the ChildView to my ChildViewModel WITHOUT setting the DataContext of my ChildView to my ChildViewModel
You can remove the Event Trigger, as Button has a Command property.
<TextBlock Text="{Binding Path=Title}" />
...
<Button
Content="Start exe"
Tag="{Binding Path=ExePath}"
Command="{Binding Path=OnButtonClicked}"
>
</Button>
And set the DataContext :
<ListBox
ItemsSource="{Binding Path=Items}">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type vm:ChildViewModel}">
<views:ChildView Width="auto" DataContext="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

wpf Usercontrol template

In my MVVM app I have a treeview representing records in a database. My views and viewmodels are linked in a resource dictionary like this
<DataTemplate DataType="{x:Type vm:TrialSiteViewModel}">
<vw:TrialSiteView />
</DataTemplate>
I want to display a preview of the view when a user hovers over an icon using the tooltip. My HierarchicalDataTemplate in the treeview is this
<HierarchicalDataTemplate DataType="{x:Type vm:TrialSiteViewModel}"
ItemsSource="{Binding Path=Children}">
...
<Button Style="{StaticResource previewButtonStyle}">
<Button.ToolTip>
<ToolTip Style="{x:Null}">
<ToolTip.ContentTemplate>
<DataTemplate>
<localtools:ObjectPreview
PreviewObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeViewItem}}, Path=DataContext}"
/>
</DataTemplate>
</ToolTip.ContentTemplate>
</ToolTip>
</Button.ToolTip>
</Button>
</StackPanel>
</HierarchicalDataTemplate>
This correctly picks up the TrialSiteViewModel that is the DataContext for the Treeviewitem.
ObjectPreview uses a viewbox and contentcontrol to display the view of the record
<Viewbox Grid.Row="1" Name="treeviewViewBox"
Stretch="Uniform"
IsEnabled="False">
<ContentControl Name="treeViewItemViewModel"
Content="{Binding PreviewObject}">
</ContentControl>
</Viewbox>
and the code behind contains the dependency property
public partial class ObjectPreview : UserControl
{
public ObjectPreview()
{
InitializeComponent();
}
public static readonly DependencyProperty _previewObjectProperty =
DependencyProperty.Register("PreviewObject", typeof(TreeViewItemViewModel), typeof(ObjectPreview));
public TreeViewItemViewModel PreviewObject
{
get { return (TreeViewItemViewModel)GetValue(_previewObjectProperty); }
set { SetValue(_previewObjectProperty, value); }
}
}
The problem I'm having is that the Template used to display the object is the same as that used in the treeview. This simply shows an icon and an object summary (ie. Primary Key and one or two key fields) rather than the entire template as defined in the view TrialSiteView. If I amend the code to use a button Command on the TrialSiteViewModel and inject it into ObjectPreview I can set the contentcontrol in the code behind and the TrialSiteView is used.
I'm guessing that somehow the Template is inferred from the TreeViewItem. Can anyone tell me how I can ensure the tooltip uses the TrialSiteView?
UPDATE
Ok, so I've fixed this but had to resort to code behind and removed the usercontrol and put the view directly in the tooltip. The key bit is getting the datatemplate from the resources. I'd tried to do this previously by assigning a key to the datatemplate, but either my code was flawed or it did not work. Anyhow, this works but is not the preferred Xaml solution.
private void PreviewObject_MouseEnter(object sender, MouseEventArgs e)
{
Image image = (Image)sender;
var key = new System.Windows.DataTemplateKey(image.DataContext.GetType());
var datatemplate = (DataTemplate)this.FindResource(key);
ToolTip tooltip = new ToolTip();
tooltip.Style = VisualUtils.GetResource<Style>("ControlTemplates.xaml", "toolTipWithContentStyle");
tooltip.MaxWidth = 460;
ContentControl contentcontrol = new ContentControl();
contentcontrol.ContentTemplate = datatemplate;
contentcontrol.Content = image.DataContext as TreeViewItemViewModel;
Viewbox viewbox = new Viewbox();
viewbox.Stretch = Stretch.Uniform;
viewbox.Child = contentcontrol;
tooltip.Content = viewbox;
image.ToolTip = tooltip;
}
What you need to do is to specify explicitly what data template to use. In order to do that just add a template property along with the PreviewObject property in the preview control:
public static readonly DependencyProperty _previewObjectTemplateProperty =
DependencyProperty.Register("PreviewObjectTemplate", typeof(DataTemplate), typeof(ObjectPreview));
public DataTemplate PreviewObjectTemplate
{
get { return (DataTemplate)GetValue(_previewObjectTemplateProperty); }
set { SetValue(_previewObjectTemplateProperty, value); }
}
Then, in the ObjectPreview.xaml add the ContentTemplate property that is bound to the PreviewObjectTemplate property:
<Viewbox Grid.Row="1" Name="treeviewViewBox"
Stretch="Uniform"
IsEnabled="False">
<ContentControl Name="treeViewItemViewModel"
Content="{Binding PreviewObject}"
ContentTemplate="{Binding PreviewObjectTemplate}" >
</ContentControl>
</Viewbox>
And finally, give a key to your data template and specify a reference to it explicitly when you declare ObjectPreview:
<DataTemplate x:Key="FullViewTemplate" DataType="{x:Type vm:TrialSiteViewModel}">
<vw:TrialSiteView />
</DataTemplate>
...
<ToolTip Style="{x:Null}">
<ToolTip.ContentTemplate>
<DataTemplate>
<localtools:ObjectPreview
PreviewObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeViewItem}}, Path=DataContext}"
PreviewObjectTemplate="{StaticResource FullViewTemplate}"
/>
</DataTemplate>
</ToolTip.ContentTemplate>

How to fire event from button inside datagrid in silverlight and MVVM

I have button at first column in datagrid. I am using MVVM and try to bind Command to Command in ViewModel but when I click button in each row, it don't work (It don't call Command in ViewModel) but if I move that button out of datagrid it's working properly.
How can I fire event from button inside datagrid in MVVM?
Update 1:
XAML's code is:
<datagrid:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
VerticalAlignment="Center">
<Button x:Name="button" Content="View" Margin="5" DataContext="{StaticResource XDataContext}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding ViewOrganizationCommand}"
CommandParameter="{Binding ElementName=dtgOrganizations, Path=SelectedItem}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</StackPanel>
</DataTemplate>
</datagrid:DataGridTemplateColumn.CellTemplate>
ViewModel's code is:
public ViewModelCommand ViewOrganizationCommand { get; set; }
Instead of using EventTrigger, why not simply bind to Button.Command directly, like so?
<Button
...other properties...
Command="{Binding ViewOrganizationsCommand}"
CommandParameter="{Binding}"/>
That will bind the command, and set the CommandParameter to the DataContext of the Button, which presumably is a good thing to bind the parameter to. If it's not, just bind CommandParameter to something else that helps you uniquely identify the specific row being clicked on.
This article brings the solution with DataContextProxy. Applying this solution makes possible to write a button code like Austin Lamb's answer.
The command binding using the Event Trigger looks fine (This is how i always do it),
But I suspect that your command is never assigned to (You are using Automatic Properties)
I usually do it like this:
private ViewModelCommand viewOrganizationCommand;
public ViewModelCommand ViewOrganizationCommand
{
get
{
if (viewOrganizationCommand == null)
viewOrganizationCommand = new ViewModelCommand(Action, CanDoIt);
return viewOrganizationCommand;
}
}
try setting you Button's DataContext to the StackPanel and set the Command and CommandParameter of the Button.
<datagrid:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
VerticalAlignment="Center" DataContext="{StaticResource XDataContext}">
<Button x:Name="button" Content="View" Margin="5" Command="{Binding ViewOrganizationCommand}" CommandParameter="{Binding ElementName=dtgOrganizations, Path=SelectedItem}" >
</Button>
</StackPanel>
</DataTemplate>
</datagrid:DataGridTemplateColumn.CellTemplate>
I solved this problem by use EventToCommand behavior in MVVMLight Toolkit
Each row of the DataGrid has it's DataContext set to that object. If the ItemSource for the grid is an ObservableCollection, each row's DataContext is the Organization object.
There are two ways to handle this.
Create a wrapping or extension ViewModel that exposes the command. Then the command should fire.Here is some psuedo code.
public class OrganizationExtensionViewModel
{
<summary>
/// Private currentOrginization property.
/// </summary>
private Organization currentOrginization;
public Organization CurrentOrganization
{
get
{
return this.currentOrginization;
}
set
{
if (this.currentOrginization != value)
{
this.currentOrginization = value;
this.RaisePropertyChanged("CurrentOrganization");
}
}
}
public ViewModelCommand ViewOrganizationCommand { get; set; }
public OrganizationExtensionViewModel(Organization o)
{
this.CurrentOrganization = o;
this.ViewOrganizationCommand = new ViewModelCommand(this.ViewOrgnaizationClicked);
}
}
Define the ViewModel in the xaml as a StaticResource and refer to it as the binding path.
...
In the grid
<Button x:Name="button" Content="View" Margin="5" Command="{Binding ViewOrganizationCommand, Source={StaticResource viewModel}}" />

Resources