ReportViewer in MVVM WPF - c#

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>

Related

Blazor InputSelect OnChange trigger UI Update

I am not quite sure if I am asking the right question. I assume other people have had this issue.
I built my own Blazor Grid component. I am using an bound to a property.
I have a function to load my grid. I changed my bound property to a full getter,setter. In the setter, I call my function to load the grid. This works fast and easy in pretty much all instances. But, I have one grid that when binding it will take a few extra seconds to complete.
The problem: I can't seem to figure out how to get my waiting spinner component to show when loading my grid.
Example Blazor Markup:
#if (dataGrid == null)
{
<hr />
<BitcoSpinner></BitcoSpinner>
}
else
{
<BitcoGrid TheGrid="dataGrid"></BitcoGrid>
}
Here is my property and GridLoading:
private string selectedGroup1 = "";
public string selectedGroup
{
get => selectedGroup1;
set
{
selectedGroup1 = value;
LoadGrid();
}
}
private void LoadGrid()
{
dataGrid = null;
PT_Grid_Admin ptGrid = new PT_Grid_Admin(permitTraxLibrary, gridParams);
dataGrid = ptGrid.ADMIN_FeeList(feeList.Fee_Key, selectedGroup);
}
You should define LoadGrid method asynchronously. Therefore, at the beginning of the program, when the data grid value is set, your spinner will be displayed until the data grid value is not received. Then, after receiving the data grid value, the else part of the condition will be executed and its value will be displayed to the user.
It may not take much time to receive information from the DB in local mode, so the following code can be used to simulate the delay:
System.Threading.Thread.Sleep(5000);
In general, I think that if your code changes like this, you can see the spinner.
private string selectedGroup1 = "";
public string selectedGroup
{
get => selectedGroup1;
set
{
selectedGroup1 = value;
LoadGrid();
}
}
private async Task LoadGrid()
{
dataGrid = null;
System.Threading.Thread.Sleep(5000);
.
.
}
Of course, it is better to load the datagrid in OnInitializedAsync method. For more info you can refer to this link.

Pass data from User Control A to User Control B within a Form [duplicate]

This question already has answers here:
Communicate between two windows forms in C#
(12 answers)
Closed 2 years ago.
To masters out there, I'm having problem with passing values 2 user controls within a form. I have usercontrol A which has combobox and a button and control B that has a datagridview. Now I want to display the data in User Control B which is in the datagridview. How can I pass the data from UCA to UCB?
Here's the data I want to pass:
So in User Control A when I clicked the button named Generate, it will fill the datagridview in User Control B with the data generated in the GetConvo() below.
public DataTable GetConvo() {
DataTable table = new DataTable();
table.Columns.Add("ConvoUser", typeof(Object));
table.Columns.Add("Message", typeof(Object));
table.Columns.Add("Date", typeof(Object));
var _data = from data in User.GetMatchConvo(comboBox3.SelectedValue.ToString())
select new {
convoUser = data.actor_id,
convoMessage = data.message,
convoDate = data.creation_timestamp
};
foreach (var data in _data) {
table.Rows.Add(data.convoUser, data.convoMessage, data.convoDate);
}
//dataGridView1.AllowUserToAddRows = false;
//dataGridView1.AllowUserToDeleteRows = false;
return table;
}
private UserInterface User = new UserData();
UserControlA knows/should know UserControlB?
Then create a property of type of UserControlB in UserControlA, then whenever you want to pass data to UserControlB, use the instance which you have in UserControlB property.
Which is which? Right, Maybe the BindingNavigator and BindingSource example is a bit clearer:
public class BindingNavigator
{
Public BindingSource BindingSource { get; set; }
public int Position
{
get {return BindingSource?.Position ?? -1};
set {if(BindingSource!=null) BindingSource.Position = value;}
}
}
Then when you dropped an instance of the BindingNavigator and an instance of the BindingSource on the form, set BindingSource property of the BindingNavigator to bindingSource1.
UserControlA doesn't/shouldn't know UserControlB?
Use events. It's the most natural way. Every day you are using it, like TextChanged, SelectedIndexChanged, and so on. Time to create one for your user control.
In fact you need to raise event in UserControlA, then on the form, when you dropped an instance of UserControlA and an instance of UserComtrolB, handle UserControlA event and set UserControlB property.
To make it a bit clearer, again with BindingNavigator and BindingSource:
public class BindingNavigator
{
public event EventHanlder MovingNext;
public void MoveToNextRecord()
{
MovingNext?.Invoke(this, EventArgs.Empty);
}
}
Then when you dropped an instance of the BindingNavigator and an instance of the BindingSource on the form, handle MovingNext event of bindingNavigator1 and set position of the bindingSource1:
bindingNavigator1.MovingNext += (obj, args) => {
bindingSource1.Position +=1;
};
Want to learn more about events? Take a look at the following documentations:
Handling and raising events
Standard .NET event pattern
you can create a static variable in usercontrol and pass data to this variable
in UserControl A or B Create a variable
public static string info = string.empty;
and before you open usercontrol you must pass data to variable
UsercontrolA.info = "hello";
new UsercontrolA();
UPDATE
create a static instance of usercontrol in UsercontrolA
public static internal UsercontrolA uc;
now in you ctor
public UsercontrolA(){
InitializeComponent();
uc = this;
}
You now need a function to perform the display operation
public void showData(){
// your display codes
messagebox.show(info);
}
And at the end, after you click button, also call the display function.
UsercontrolA.info = "hello";
new UsercontrolA();
UsercontrolA.us.showData();
I didn't test the codes but it definitely should work

Update WPF with externalevent and MVVM schema

I'm currently developping a new soft which analyze a lot of data on Revit.
I have my WPF View, my ViewModel and a long methode.
init.cs:
IExternalEventHandler handlerEvent = new FamilyAnalysis();
var familyAnalysisEvent = ExternalEvent.Create(handlerEvent);
Gui = new AnalysisView(familyAnalysisEvent);
Gui.Show();
AnalysisView.xaml.cs:
public AnalysisView(ExternalEvent externalEvent)
{
InitializeComponent();
Windows.SetLocation(this);
Language = System.Windows.Markup.XmlLanguage.GetLanguage(System.Threading.Thread.CurrentThread.CurrentCulture.Name);
DataContext = new AnalysisViewModel(externalEvent, this);
}
AnalysisViewModel.cs:
public ICommand StartAnalysisCommand
{
get
{
return _startAnalysisCommand ?? (_startAnalysisCommand = new RelayCommand(() =>
{
//Some check before then..
_externalEvent.Raise();
}));
}
}
during the _externalEvent.Raise() I would like to have a loading form updating the state of the methode.
I tried to search about dispatcher but I'm not sure how to do that and all my try didn't worked. The loading screen pop but the value are not getting updated.
Any advice ?

Caliburn Micro Screen Activated Events

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.

Problems with Syncfusion GridTreeControl and Binding

I'm new to WPF and am using the Syncfusion Framework. I want to use the DataTreeControl to display a hierarchy of data which will be loaded and updated in a reoccuring interval. But for some reason it doesn't display the data.
Here's a snipped from my MainWindow.xaml
<syncfusion:TabItemExt Name="_tabItemTipps" Header="Tipps">
<syncfusion:GridTreeControl Name="_treeGrid"
BorderBrush="LightGray"
BorderThickness="0,0.5,0,0"
EnableHotRowMarker="False"
EnableNodeSelection="True"
ExpandStateAtStartUp="AllNodesExpanded"
ReadOnly="True"
SupportNodeImages="True"
VisualStyle="Metro"
ItemsSource="SoccerMarkets"
>
<!-- Code for GridTreeControl Columns -->
<syncfusion:GridTreeControl.Columns>
<syncfusion:GridTreeColumn HeaderText="Nation" MappingName="{Binding RoughCat}"></syncfusion:GridTreeColumn>
</syncfusion:GridTreeControl.Columns>
</syncfusion:GridTreeControl>
This the snippet from MainWindow.xaml.cs where the DataContext is set:
public MainWindow()
{
DataContext = this;
InitializeComponent();
SkinStorage.SetVisualStyle(_tabControl, "Metro");
_settingsVM = new AppSettingsVM();
_txtBetdaqUser.DataContext = _settingsVM;
_chkSystemActive.DataContext = _settingsVM;
_chkInSimulationMode.DataContext = _settingsVM;
_mechanic = new TippMechanic(_settingsVM);
_soccerMarketsVM = new SoccerMarketVM();
Task[] tasks = new Task[1];
tasks[0] = Task.Factory.StartNew(async () => await _mechanic.Init());// _mechanic.Init();
Task.WaitAll(tasks);
_soccerMarketsVM.SoccerMarkets = _mechanic.SoccerMarketManager.SoccerMarkets;
_treeGrid.DataContext = _soccerMarketsVM.SoccerMarkets;
}
My ViewModel (_soccerMarketsVM) is defined this way:
class SoccerMarketVM : ObservableObject
{
private ObservableCollection<SoccerMarket> _soccerMarkets;
public ObservableCollection<SoccerMarket> SoccerMarkets
{
get { return _soccerMarkets; }
set
{
if(_soccerMarkets != null)
_soccerMarkets.CollectionChanged -= _soccerMarkets_CollectionChanged;
_soccerMarkets = value;
_soccerMarkets.CollectionChanged += _soccerMarkets_CollectionChanged;
}
}
public SoccerMarketVM()
{
//_soccerMarkets = new ObservableCollection<SoccerMarket>();
//_soccerMarkets.CollectionChanged += _soccerMarkets_CollectionChanged;
}
void _soccerMarkets_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
Console.WriteLine(e.Action.ToString());
}
}
The Events for CollectionChanged are fired and I get the Console.Writeline output.
Does anyone see's something wrong here?
In GridTreeControl, you can populate the data using different ways. In your code snippet, ItemsSource defined without specifying the Binding keyword and the MappingName is defined with Binding keyword. But for itemssource, you need to specify binding and for mapping name, you can directly assign property name without specifying binding. Please refer the below UG link of data population in GridTreeControl,
Link:
http://help.syncfusion.com/ug/wpf/index.html#!Documents/addingthegridtreecontroltoawpfapplication.htm
Elavarasan M – Syncfusion Software.

Categories

Resources