Routed Command Implementation in WPF - c#

I have simple ICommand-Bindings working, however I have Buttons inside an ItemsControl and wanted to get the sender information, like with the normal Routed-Events (object sender, e RoutedEventArgs) and this seems not to be possible with the normal ICommands, right?
I am a little bit lost here.
I currently use the Prism 6 DelegateCommand-Class to get things working. It looks like this:
private ICommand _selectCommand;
public ICommand SelectCommand
{
get
{
return _selectCommand ?? (_selectCommand = new DelegateCommand<object>(SelectImage));
}
}
private void SelectImage(object image)
{
var img = (BitmapImage)image;
var index = Scans.IndexOf(img);
this.CurrentIndex = index + 1;
ImageToDisplay = img;
}
How I can I get the RoutedCommand to work?

A view model is not supposed to be accessing or even know about any view element.
You should bind a target property of the control in the view to a source property of the view model that you can simply set in your SelectImage method when your command gets executed.

Related

Retrieve the DataContext of the Content set by using ContentSource property

I need to get the DataContext of the View set by using ContentSource property of the ModernWindow, Could you please help.I am using MVVM framework with Modern UI. The ViewModel code from where I need to show another window is as follows,
public void ShowPrompt()
{
this.PromptWindow = ObjectFactory.GetInstance<IPromptWindowViewModel>().Window as ModernWindow;
this.PromptWindow.Owner = Application.Current.MainWindow;
this.PWPMainViewModel.PromptWindowsCollection.Add(this.PromptWindow);
// Here I need to get the DataContext of PromptWindow's Content
this.PromptWindow.Show();
}
I did some debugging and found that by inherting IContent interface from ModernUI in the 'OnNavigatedTo' event
public void OnNavigatedTo(FirstFloor.ModernUI.Windows.Navigation.NavigationEventArgs e)
{
IPWPMainViewModel pwpMainViewModel = ObjectFactory.GetInstance<IPWPMainViewModel>();
pwpMainViewModel.PromptMainsCollection.Add(new ContentControl { Content = e.Content });
IPromptMainViewModel promptMainViewModel = ((UserControl)e.Content).DataContext as IPromptMainViewModel;
}
Here I am able to get the DataContext of the ModernWindow's Content i.e. of type 'IPromptMainViewModel' but here its very difficult to map/load the views into this ModernWindow as there are multiple instances of views, but I would like to do it in the ViewModel where 'ShowPrompt()' is present as there the Model will be associated with the View correctly so I can map there the views easily.
Thank you.
To get this done, I set the Content of the ModernWindow by myself (as shown in below code in a method in a ViewModel) without using the ContentSource DependencyProperty, If we use the ContentSource property it will be set for a ModernFrame type by the ModernWindow itself creating its Content instance after Navigation to that View completes in some method in ModernFrame class from ModernUI for WPF by using ModernFrame's Source DependencyProperty.
public void ShowPrompt()
{
this.PromptWindow = ObjectFactory.GetInstance<IPromptWindowViewModel>().Window as ModernWindow;
this.PromptWindow.Title = string.Concat("Control ", this.PromptOriginsEntity.PromptOriginsIdentity);
this.PromptWindow.Tag = this.PromptOriginsEntity.PromptOriginsIdentity;
this.PromptWindow.Owner = Application.Current.MainWindow;
// Store Window object in PromptWindowsCollection
this.PWPMainViewModel.PromptWindowsCollection.Add(this.PromptWindow);
this.PromptWindow.Show(); // inorder to retrieve the ModernFrame the ModernWindow is to be shown first
ModernFrame frameContent = (ModernFrame)this.PromptWindow.Template.FindName("ContentFrame", this.PromptWindow);
UserControl userControl = new UserControl { Content = GetView<IPromptViewModel>(), Tag = this.PromptOriginsEntity.PromptOriginsIdentity };
frameContent.Content = userControl;
this.PWPMainViewModel.PromptsCollection.Add(userControl);
IPromptViewModel promptViewModel = (IPromptViewModel)((IView)userControl.Content).DataContext;
promptViewModel.PromptEntity.Identity = this.PromptOriginsEntity.PromptOriginsIdentity;
}
I've uploaded a prototype app at https://wpfmvvmsamples.codeplex.com/SourceControl/latest
Thanks.

Binding data MVVM after Tap on ListBox

This is my situation:
- I have a WebApi that sends me a json data.
- My app reads this data and binds all information in a list box
- When I tap on Item of the list box I want to show all information about that item
The problem is: How can I bind data on the new view?
This is the code after tap (MainViewModel):
this.ProfessorDetail = new RelayCommand(() =>
{
if (SelectedIndexProfessors != -1)
{
//This variable contain all detail information
Professor x = Professors.ElementAt(SelectedIndexProfessors);
//Open new page
App.RootFrame.Navigate(new Uri("/Pages/ProfessorDetailPage.xaml", UriKind.RelativeOrAbsolute));
}
});
You can use Uri parameter to pass simple string information between pages. For example, in your RelayCommand pass unique information about selected professor :
.........
//pass selected professor Id to ProfessorDetailPage
App.RootFrame.Navigate(new Uri("/Pages/ProfessorDetailPage.xaml?professorId=" + x.ProfessorId, UriKind.RelativeOrAbsolute));
.........
Then in the ProfessorDetailPage's Loaded or NavigatedTo event handler get the uri parameter and display information accordingly :
.........
string professorId;
if(NavigationContext.QueryString.TryGetValue("professorId", out professorId))
{
//load information based on professorId parameter value
}
.........
I never made a page based application, but I was looking at this link and from basic knowledge of WPF, I do know you need to set the DataContext of that page just like you asked.
Take a look at the following code in the link:
this.Frame.Navigate(typeof(BasicPage2), tb1.Text);
That is sending the text entered in tb1 to the object you're navigating to. Then look at how the receiving object is utilizing that information:
private void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
string name = e.NavigationParameter as string;
....
}
You're going to want to follow the same idea, except you're going to want to do something like:
App.RootFrame.Navigate(new Uri(".....", ...), x);
And then in the page itself, you're going to want to set up a LoadState event in ProfessorDetailPage control and inside it do:
Professor prof = x as Professor;
if( prof != null)
{
this.DataContext = prof;
}
That should set the DataContext of ProfessorDetailPage and your data should be populated.
Let me know how it works out, hope this helps!

Insert Text and Image in HubTile after online-content is loaded

I'm new to WP8 and follow many tutorials. For parts of the menu I use a viewModel with NotifyPropertyChanged. When I get my list of news articles it creates a viewModel and displays it in a longListSelector.
But also I want to make 1 HubTile with the image and some preview-text of the first article. Is there a nice way to send some event to the .xaml.cs? Or do I have to make another viewModel for this one HubTile and make a binding?
Ony try was to make such a variable:
private bool _isDataLoaded = false;
public bool IsDataLoaded
{
get
{
return _isDataLoaded;
}
set
{
if (value != _isDataLoaded)
{
_isDataLoaded = value;
NotifyPropertyChanged("IsDataLoaded");
}
}
}
The same thing is used with "IsLoading"-variable to create a loading-indicator in the systemTray:
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("MainPage_Loaded-Funktion");
Binding binding = new Binding("IsLoading") { Source = DataContext };
BindingOperations.SetBinding(
prog, ProgressIndicator.IsVisibleProperty, binding);
binding = new Binding("IsLoading") { Source = DataContext };
BindingOperations.SetBinding(
prog, ProgressIndicator.IsIndeterminateProperty, binding);
prog.Text = "Lade aktuelle Inhalte...";
}
Can I use this to call a function, when my variable is set and I get a notification?
The solution that helped me out was this:
<toolkit:HubTile Message="{Binding OnlineNews[0].TeaserText}"/>
Didn't know that you can access the viewModel like that. Thanks to Toni Petrina!

How to bind Variables of View to ViewModel in WPF MVVM?

I have a created a window ( WPF and MVVM ) - say PrintWidow ( so I have PrintWindow.xaml , PrintWindow.xaml.cs , PrintWindowViewModel.cs- viewmodel)
Now I am going to use(call) this PrintWindow obj from some other class on button click or on some command trigger , I want to set Document Source for this PrintWindow(following MVVM).
How would I do this ? I created a PrintDocument object in PrintWindow.xaml.cs and tried to bind it as follows : (obviously just a blank try - as I cannot do this declaration in XAML)
private PrintDocument printDocuementView;
public PrintDocument PrintDocuement
{
get { return printDocuementView; }
set { printDocuementView = value; }
}
//constructor
public PrintWindow()
{
InitializeComponent();
this.DataContext = new PrintViewModel();
Binding b = new Binding();
b.Source = printDocuementView;
b.Path = new PropertyPath("PrintDocumentCommand"); // "PrintDocumentCommand" is defined in View Model class and is responsible to set the `PrintDocument` object there.
}
This code (obviously) doesn't work. How should I go about it.
Summary : I want to open PrintWindow from another window and eventually set some Property of PrintWindow from code behind of the 'other widow' object.The query is - where should this property go? View ? ViewModel? ?? puzzzeled
I have googled for answers - but couldn't relate any to my problem.
I am a Freshman for WPF and a Rookie for MVVM.
Since your PrintDocumentCommand is in your PrintViewModel but you're setting the Source of this Binding to an instance of the PrintDocument-Class, it can't be found, because the Binding is looking for the PrintDocumentCommand in PrintDocument-Class.
If you want to open the PrintWindow from another Window, place the PrintDocument-Property and the PrintDocumentCommand in the ViewModel of the other Window. Now your function that is executed through the PrintDocumentCommand could look like:
private void Print()
{
PrintWindow pw = new PrintWindow(PrintDocument);
pw.ShowDialog();
}
The constructor of your PrintView could be like:
public PrintWindow(PrintDocument pd)
{
InitializeComponents();
this.DataContext = new PrintViewModel(pd);
}
and now you can access the PrintDocument in your PrintViewModel.

Multiple Views of Observable Collection with Datagrid

I have the same problem like this. But I´m using a DataGrid instead of a ListBox and it does not seem to work like this (it might also be because i never used visual basic and didnt translate the code correcly into c#).
I basicly want two DataGrids on the same data with different filters.
ICollectionView view_dataLinesUnfiltered;
ICollectionView view_dataLinesFiltered;
public MainWindow()
{
...
//view_dataLines = CollectionViewSource.GetDefaultView(dataLines); // <- Filter works on both
view_dataLinesUnfiltered = new CollectionView(dataLines); // <- Filter doesn´t work at all
view_dataLinesFiltered = new CollectionView(dataLines);
....
// Control Events
this.ShowAA.RaiseEvent(new RoutedEventArgs(System.Windows.Controls.Primitives.ToggleButton.UncheckedEvent));
}
private void ShowAA_Checked(object sender, RoutedEventArgs e)
{
view_dataLinesUnfiltered.Filter = null;
}
private void ShowAA_UnChecked(object sender, RoutedEventArgs e)
{
view_dataLinesUnfiltered.Filter = delegate(object o) { return FilterContent(o as ErrorDetection.stDataLine, "AA", ""); };
}
bool FilterContent(ErrorDetection.stDataLine line, string sFilterAA, string sFilter)
{
shortArrayToHexStringConverter converter = new shortArrayToHexStringConverter();
string comBuffer = converter.Convert(line.ComBufferP as object,typeof(string),0,System.Globalization.CultureInfo.CurrentCulture) as string;
return false;// !comBuffer.Contains("AA");
}
The FilterContent method is being called without problems, but the DataGrid shows the lines anyway. If I use GetDefaultView the Filter works on both Datagrids. Do I have to use some other view instead of CollectionView (ListCollectionView does also not work)?
i have made a small sample project to show the problem sample. It only consists of an constructor and an observable collection.
I got it to work somehow. I used CollectionViewSources now instead of ICollectionView.
<Window.Resources>
<CollectionViewSource x:Key="viewSource_dataLinesUnfiltered"/>
<CollectionViewSource x:Key="viewSource_dataLinesFiltered"/>
</Window.Resources>
...
<DataGrid Name="Filtered_Datagrid" ItemsSource="{Binding Source={StaticResource viewSource_dataLinesFiltered}}" >
...
</DataGrid>
...
<DataGrid Name="Unfiltered_Datagrid" ItemsSource="{Binding Source={StaticResource viewSource_dataLinesUnfiltered}}">
...
</DataGrid>
and the c Code:
CollectionViewSource viewSource_dataLinesUnfiltered;
CollectionViewSource viewSource_dataLinesFiltered;
...
public MainWindow()
{
InitializeComponent();
this.DataContext = dataLines;
viewSource_dataLinesUnfiltered = (CollectionViewSource)this.Resources["viewSource_dataLinesUnfiltered"];
viewSource_dataLinesUnfiltered.Source = dataLines;
viewSource_dataLinesFiltered = (CollectionViewSource)this.Resources["viewSource_dataLinesFiltered"];
viewSource_dataLinesFiltered.Source = dataLines;
// Control Events
this.ShowAA.RaiseEvent(new RoutedEventArgs(System.Windows.Controls.Primitives.ToggleButton.UncheckedEvent));
}
private void ShowAA_Checked(object sender, RoutedEventArgs e)
{
viewSource_dataLinesUnfiltered.View.Filter = null;
}
private void ShowAA_UnChecked(object sender, RoutedEventArgs e)
{
viewSource_dataLinesUnfiltered.View.Filter = delegate(object o) { return FilterContent(o as ErrorDetection.stDataLine, "AA", ""); };
}
bool FilterContent(ErrorDetection.stDataLine line, string sFilterAA, string sFilter)
{
shortArrayToHexStringConverter converter = new shortArrayToHexStringConverter();
string comBuffer = converter.Convert(line.ComBufferP as object,typeof(string),0,System.Globalization.CultureInfo.CurrentCulture) as string;
return !comBuffer.Contains("AA");
}
But I´m not sure why it works this way and the filter is not applied on window repaints when ICollectionView is used.
You need to specify which ICollectionVIew is used on which DataGrid.
If you just bind to the collection (dataLines in this case) WPF will use the 'default view' (or create one if necessary), this is why the first commented out line works for filtering.
There are a few ways you could specify which view is used for which datagrid, depending on what patterns, etc. you are using
1) Like the linked question, you could set the ItemsSource for each DataGrid in the window's code behind, after initializing the views, e.g.:
filteredDataGrid.ItemsSource = view_dataLinesFiltered;
unfilteredDataGrid.ItemsSource = view_dataLinesUnfiltered;
2) You could set the DataContext of the window to itself, or make a view model for the screen that contains the view, and make the views public properties and then bind to the intended view for each grid, e.g.
<DataGrid ItemsSource="{Binding View_dataLinesFiltered}"> ....
Edit:
Now I'm not at work and can get to dropbox and play with your example it seems like the cause of the weird behaviour is the use of CollectionView directly. On the msdn page for CollectionView it says
You should not create objects of this class in your code. To create a
collection view for a collection that only implements IEnumerable,
create a CollectionViewSource object, add your collection to the
Source property, and get the collection view from the View property.
However, if you don't want to set up the views in XAML, you could also change your CollectionViews to ListCollectionViews and it should work as expected (this is likely the view type that CollectionViewSource is making for you behind the scenes anyway).

Categories