I'm using Caliburn Micro and AvalonDock in my project. I try to handle events when screen was activated. My main view model is a 'Conductor.Collection.OneActive' and each tab "document" is a 'Screen'.
I have a function in my main view model like this:
public void CheckAndRegisterDocument(DocumentViewModel document)
{
DocumentViewModel exists = _documents.FirstOrDefault((d) => { return d.Equals(document); });
// if not exists - add it
if(exists == null) {
document.Activated += Document_Activated;
_documents.Add(document);
Items.Add(document);
}
// activate and set property object
ActivateItem(document);
Properties.PropertiesObject = document.Properties;
}
// document activated event handler
private void Document_Activated(object sender, ActivationEventArgs e) {
ActiveDocument = sender as DocumentViewModel;
}
But the function "Document_Activated" is not invoked. what am I doing wrong?
Instead of adding your document objects into a documents collection, add them to the already existing this.Items collection.
Also, each document object will need to inherit from Screen for it to participate.
That +should+ be enough to do the trick, but sometimes it can be necessary to tell Caliburn to "conduct" your viewmodels via ConductWith...
document.ConductWith(this)
there this is the current conductor viewmodel.
Related
I'm having trouble iterating over my inflated custom RowView in the ListView of my Activity class and assigning the proper events to controls on the inflated custom RowView.
Yes, I can add events to the controls in the Adapter class GetView() override, but I think there are a couple problems with that.
I'm under the impression all application logic should live in the
Activity class and the adapter should just be used for data binding. If I add all the navigation logic, CRUD operations, etc, to the controls in the Adpater nothing would live in the activity and the Adapter would now be responsible for application logic, event handling, database helper class interaction, etc.
I don't have access to StartActivity() or this in the Adapter
class, so the Adapter doesn't seem be the place to add navigation logic,
e.g. I want a button on the row to open the "Detail View", but can't if I
cannot call StartActivity() or pass in the proper parameters to
Intent().
So, I thought I'd just add the events to the controls in the Activity class. Next problem, in the activity class, I cannot figure out how to iterate over the ListView items, e.g. there is no ListView.Items collection to iterate over and add the event handlers to the various controls.
I've been walking through several Pluralsight courses and examples elsewhere and typically those have just had event handlers on the ListView_ItemClick, which is added in the Activity class. That's simple enough and I have that implemented and working, but I'm unable to achieve the same for the controls on the inflated custom RowView
Is there a way to iterate over the inflated custom RowViews of a ListView? Am I mistaken about what code should live in the Activity vs Adapter?
Implement the View.IOnClickListener interface in your adapter
add this in your adapter
private InnerItemOnclickListener mListener;
public interface InnerItemOnclickListener
{
void itemClick(View v);
}
public void SetOnInnerItemOnClickListener(InnerItemOnclickListener listener)
{
this.mListener = listener;
}
public void OnClick(View v)
{
mListener.itemClick(v);
}
3.in your adpater's BindData() method ,add this:
deleteName.SetOnClickListener(this);
editName.SetOnClickListener(this);
4.in your activity,implement the BabyNameListAdapter.InnerItemOnclickListener and AdapterView.IOnItemClickListener interface
5.add this in your activity:
private BabyNameListAdapter Adapter;
public void itemClick(View v)
{
int position;
position = (int)v.Tag;
switch (v.Id)
{
case Resource.Id.editNameButton:
System.Diagnostics.Debug.Write("editNameButton click"+" position="+position);
break;
case Resource.Id.deleteNameButton:
System.Diagnostics.Debug.Write("deleteNameButton click" + " position=" + position);
break;
default:
break;
}
}
public void OnItemClick(AdapterView parent, View view, int position, long id)
{
System.Diagnostics.Debug.Write("RowView click");
}
6.replace your BindData() with this:
private void BindData()
{
Adapter = new BabyNameListAdapter(this, allBabyNames);
Adapter.SetOnInnerItemOnClickListener(this);
babyNameListView.Adapter=Adapter;
}
7.replace babyNameListView.ItemClick += BabyNameListView_ItemClick; with babyNameListView.OnItemClickListener = this;
8.run your project.
Ok,now you can click your listview's rowview and the button on the rowview, and it will work well.
You can see that I use the interface to set the click event not in adapter but in activity. There is no clear boundaries where to write you logic code , only one thing which need to care is the performence .
You of course can use startActivity() in your adapter if you want to pass the parameters to your adapter ,but it will complex ,so use interface you can handler this in your activity.
I hope it will help you.
I'm building an MVVM Light WPF app using Visual Studio 2015. The app needs to display some SQL Server Reporting Services reports locally.
The following two solutions exist:
Using MS ReportViewer in WPF
Walkthrough: Using ReportViewer in a WPF Application
Though the first is MVVM, it's mixing UI with the view model. The second is pure code-behind.
Here's what the first example suggests:
WindowsFormsHost windowsFormsHost = new WindowsFormsHost();
reportViewer = new ReportViewer();
windowsFormsHost.Child = reportViewer;
this.Viewer = windowsFormsHost
Note that ReportViewer is a UI control. The second solution uses a code-behind file:
private void ReportViewer_Load(object sender, EventArgs e)
{
//...
}
Is there a way to embed a local SSRS report into a WPF app and follow good MVVM practices? Thank you.
Update: No need to be fanatical! If some code-behind is needed, I'm okay with it.
We use a view to select the report from a ComboBox and a button to run it. In the viewmodel, we have the reports' ComboBox bound to an ObservableCollection of report names and IDs. We then employ the MVVM Light Toolkit's Messaging class to send/receive "messages." Note that the base viewmodel, MyAppViewModelBase, inherits from Light Toolkit's ViewModelBase, which has the RaisePropertyChanged() defined.
Also note that we could pass the selected report's VM instead of the view's VM; that would be more efficient but will require modifications to this code. Then we'd use a base class for all the report VMs and a pattern-matching switch in the code-behind to select which report to run.
Here's the pertinent code for the viewmodel:
using GalaSoft.MvvmLight.Messaging;
public class ReportsViewModel : MyAppViewModelBase
{
public ReportsViewModel()
{
// Register a listener that receives the enum of the
// report that's ready. The message it receives has
// name "SsrsReportReady" with handler SsrsReportReady.
Messenger.Default.Register<Constants.Report>(this, "SsrsReportReady", SsrsReportReady);
// Other logic...
}
// Bound to a button to run the selected report
public ICommand RunReportRelayCommand =>
new RelayCommand(RunReport);
// Backing field for the selected report.
private ReportViewModel _selectedReportVm;
public ReportViewModel SelectedReportVm
{
get { return _selectedReportVm; }
set
{
if (Equals(value, _selectedReportVm)) return;
_selectedReportVm = value;
// Built-in method from Light Toolkit to
// handle INotifyPropertyChanged
RaisePropertyChanged();
}
}
private void RunReport()
{
// Send a message called "RunSSRSReport" with this VM attached as its data.
Messenger.Default.Send(this, "RunSSRSReport");
}
// Handler for report-ready
private void SsrsReportReady(Constants.Report obj)
{
ShowReport = true;
IsRunReportButtonEnabled = true;
RunReportButtonContent = Constants.BtnGenerateReport;
// View uses Material Design's Expander control.
// We expand/collapse sections of the view.
ExpandReport = true;
ExpandParameters = false;
}
}
In the code-behind of the view:
using GalaSoft.MvvmLight.Messaging;
public partial class ReportsView : UserControl
{
public ReportsView()
{
InitializeComponent();
// Register a listener for the "RunSSRSReport"
// message, called from our viewmodel. Its
// handler is RunSsrsReport and its data type
// is ReportsViewModel.
Messenger.Default.Register<ReportsViewModel>(this, "RunSSRSReport", RunSsrsReport);
DataContext = new ReportsViewModel();
}
// Handler to run the selected report.
private void RunSsrsReport(ReportsViewModel obj)
{
// Basic validation
if (obj.SelectedReportVm == null || obj.SelectedReportVm.Id.Equals(-1))
{
return;
}
// Ugly switch to run the correct report.
// It can be re-written with pattern matching.
switch (obj.SelectedReportVm.Id)
{
case (int)Constants.Report.ReportA:
RunReportA(obj);
break;
case (int)Constants.Report.ReportB:
RunReportB(obj);
break;
// other reports....
}
}
// Run the report using dataset and tableadapter.
// Modify to use your code for running the report.
private void RunReportA(ReportsViewModel reportsViewModel)
{
var dataSet = new ReportADataSet();
dataSet.BeginInit();
// We reference the ReportViewer control in XAML.
ReportViewer.ProcessingMode = ProcessingMode.Local;
ReportViewer.LocalReport.ShowDetailedSubreportMessages = true;
ReportViewer.LocalReport.DataSources.Clear();
var dataSource = new ReportDataSource
{
Name = "ReportA_DS",
Value = dataSet.uspReportA // Uses a stored proc
};
ReportViewer.LocalReport.DataSources.Add(dataSource);
ReportViewer.LocalReport.ReportEmbeddedResource =
"MyApp.Reports.ReportA.rdlc";
dataSet.EndInit();
new reportATableAdapter { ClearBeforeFill = true }
.Fill(dataSet.uspReportA);
// Send message back to viewmodel that the report is ready.
Messenger.Default.Send(Constants.Report.ReportA, "SsrsReportReady");
}
}
The report view has a WindowsFormsHost with name ReportViewer, referenced in above code-behind:
<WindowsFormsHost Width="Auto" Height="500">
<rv:ReportViewer x:Name="ReportViewer" />
</WindowsFormsHost>
I am developing a WinRT 8.1 application and I have a MenuFlyout within my custom control. Essentially, when a user clicks an Item within the MenuFlyout, the user is navigated to a different page. My dilemma is that I cannot access the Page element within my user control. Is there any work-around for this? I have looked at many similar SO questions, but none of them worked for me.
public sealed partial class BottomAppBar : UserControl {
public BottomAppBar() {
this.InitializeComponent();
//we are forced to manually add items as flyout does not support command
foreach (Vault v in User.Instance.Vaults) {
MenuFlyoutItem vault = new MenuFlyoutItem();
vault.Text = v.Name;
vault.Click += switchUser;
flyoutVault.Items.Add(vault);
}
}
private void switchUser(object sender, object e) {
//This line results in an error
this.Frame.Navigate(typeof(LoginPage));
/** Does not work as well
var parent = VisualTreeHelper.GetParent(this);
while (!(parent is Page)) {
parent = VisualTreeHelper.GetParent(parent);
}
(parent as Page).Frame.Navigate(typeof(LoginPage));
*/
}
The design-patterned solution is to create a navigation service passing the app frame to it and then use something like dependency injection to pass the navigation service to whomever might need it.
The simple solution is to store the reference to the Frame in your App class and access it through the app object/static property.
I have my ViewController like
public partial class TestView
: MvxViewController
{
...code here...
}
and i load my next ViewController on button event TouchUpInside like that:
btnSearch.TouchUpInside += (object sender, EventArgs e) => {
BTProgressHUD.Show("test");
ViewModel.GoParameterizedCommand.Execute(null);
};
that event it's defined in ViewDidLoad. My "test" message it's showed on next ViewController and not during loading of this one. How could i show that message during loading and not when next ViewController is loaded? I have tried to use also MBProgressHUD
btnSearch.TouchUpInside += (object sender, EventArgs e) => {
var hud = new MTMBProgressHUD (View) {
LabelText = "Waiting...",
RemoveFromSuperViewOnHide = true
};
View.AddSubview(hud);
hud.Show (animated: true);
ViewModel.GoParameterizedCommand.Execute(null);
};
but behaviour it's same.
It seems that you are trying to work with ProgressHUD in View layer. In MVVM-way you should create a "Loading" property in your ViewModel and bind it to progressbar in View.
Here is a good Stuart's video how to do that: http://youtu.be/6fNXGErr9to
And here is sample code: https://github.com/MvvmCross/NPlus1DaysOfMvvmCross/tree/master/N-34-Progress
I am doing something similar in one of my apps.
I have a IsLoading property in my ViewModel which I set whether something is loading and not. Then in my ViewController I am subscribing to changes on Loading like this:
ViewModel.WeakSubscribe(() => ViewModel.IsLoading, (sender, args) =>
{
if (ViewModel.IsLoading)
{
ShowLoadingDialog("Loading Resource");
}
else
{
DismissLoadingDialog();
}
});
void ShowLoadingDialog(string text)
{
DismissLoadingDialog();
BTProgressHUD.Show(text, -1, BTProgressHUD.MaskType.Black);
}
void DismissLoadingDialog()
{
if (BTProgressHUD.IsVisible)
BTProgressHUD.Dismiss();
}
I do this in ViewDidLoad(). You could do something similar in your first View, then in your second you could just call DismissLoadingDialog() or simply make sure you call ViewWillDisappear() or ViewDidDisappear(), in the first ViewModel, so it gets dismissed correctly.
Loading view controller will not take more then 'Micro Seconds'.
Loading data in the view controller will need some time and during that time you need to show that ProgressHUD.
If you are loading data in 'First View Controller' then at start loading data start ProgressHud with BTProgressHUD.Show("test"); and when your data is done loading remove that HUD from the view just before navigating to the 'Second View controller'.
And if, you are loading data in 'Second View Controller', then Do navigation first from 'First View Controller' to 'Second View Controller' and then show HUD just before loading data in Second view controller BTProgressHUD.Show("test"); and remove it after data is loaded.
I have an application build with the MVVM pattern from Josh Smith (http://msdn.microsoft.com/en-us/magazine/dd419663.aspx).
When I have several workspaces opened in my app, I want to catch the event of switching workspaces/tabs so I can save the content of the current workspace first. I have looked throught WorkspaceViewModel and ViewModelBase, but I don't know how to add that EventHandler.
I have found a solution in another post, I just had to tweak it a little bit : What is the proper way to handle multiple datagrids in a tab control so that cells leave edit mode when the tabs are changed?
Basically I have added an EventHandler on PreviewMouseDown of my TabControl generating the different Workspaces.
private void TabControl_PreviewMouseDown(object sender, MouseButtonEventArgs e) {
MainWindow_VM dc = (MainWindow_VM)this.DataContext;
if (IsUnderTabHeader(e.OriginalSource as DependencyObject))
//Do what need to be done before switching workspace
// in my case, switch the focus to a dummy control so the objectContext would save everything, even the currently focused textbox
}
private bool IsUnderTabHeader(DependencyObject control)
{
if (control is TabItem)
{
return true;
}
DependencyObject parent = VisualTreeHelper.GetParent(control);
if (parent == null)
return false;
return IsUnderTabHeader(parent);
}
You should be able to bind the "Current" item of the tab to a variable in the model. When this changes, do your work.