I'm using Microsoft's ReportViewer control in my WPF application. Since this is a WinForms component, I use the WindowsFormHost control.
I try to follow the MVVM pattern as supported by the WPF Application Framework, so I implemented a ReportViewModel which contains (amongst others) the current report name and the dataset (both can be selected by 'regular' WPF controls, that part works fine).
I'd like to be as "WPF-ish" as possible, so how would I properly set up the binding to the ReportViewer component (which is inside the WindowsFormHost control)? I need to set the ReportViewer.LocalReport.ReportEmbeddedResource property and have a call to ReportViewer.LocalReport.DataSources.Add (and possibly Clear) whenever the view models report name or dataset change. What's the proper way to do that?
Is there any chance to use one of the regular WPF binding mechanisms for that? If yes, how? If no, how would I set up the binding? (its my first 'real' WPF project, so don't be shy to post trivial solutions :) ...)
Thanks!
So far I've come up with the following solution (purly code-behind):
private void MyDataContextChanged(object sender, DependencyPropertyChangedEventArgs e) {
if (e.OldValue is ReportViewModel) {
var viewModel = e.OldValue as ReportViewModel;
viewModel.PropertyChanged -= ViewModelPropertyChanged;
}
if (DataContext is ReportViewModel) {
var viewModel = DataContext as ReportViewModel;
viewModel.PropertyChanged += ViewModelPropertyChanged;
SetReportData();
}
}
void ViewModelPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) {
if (e.PropertyName == "ReportName" || e.PropertyName == "ReportData")
SetReportData();
}
private void SetReportData() {
var viewModel = DataContext as ReportViewModel;
if (viewModel != null) {
var reportView = reportHost.Child as ReportViewer;
reportView.LocalReport.ReportEmbeddedResource = viewModel.ReportName;
reportView.LocalReport.DataSources.Clear();
reportView.LocalReport.DataSources.Add(new ReportDataSource("DataSet1", viewModel.ReportData as DataTable));
reportView.RefreshReport();
}
}
I'm still curious if there are any better solutions (I'm sure there are ...).
Related
My question is for UWP but the solution might be the same in WPF so I tagged that as well.
I'm trying to implement this extension method in a custom GridView and ListView so that when a selection changes, the selected item will always smoothly animate into view.
The extension method works great. However obtaining the UIElement container to send it does not work so great.
ListView.Items is bound to a collection in a ViewModel. So ListView.Items are not UIElements, but rather data objects. I need the SelectedItem's corresponding UIElement container.
First I tried this:
void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_scrollViewer != null && this.ItemsPanelRoot != null && this.Items.Count > 0)
{
var selectedListViewItem = this.ItemsPanelRoot.Children[this.SelectedIndex];
if (selectedListViewItem != null)
{
_scrollViewer.ScrollToElement(selectedListViewItem);
}
}
}
This works at first but is actually no good. The indices of 'ListView.ItemsPanelRoot.Children' eventually start to diverge from 'ListView.Items' as the layout is updated dynamically.
Then I tried this, which so far seems to work fine:
void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_scrollViewer != null && this.Items.Count > 0)
{
var selectedListViewItem = this.ItemsPanelRoot.FindDescendants<ListViewItem>()
.Where(x => x.Content == this.SelectedItem).FirstOrDefault();
if (selectedListViewItem != null)
{
_scrollViewer.ScrollToElement(selectedListViewItem);
}
else
{
throw new Exception();
}
}
}
The problem is that it seems incredibly expensive to do that query each time and also unsafe because there's no assurance that the container is available yet. I feel (hope) I'm missing something and that there's a proper way to do this.
Note: 'FindDescendants' is an extension method from Windows UI Toolkit, does the same thing as VisualTreeHelper.
ItemsControls have an ItemContainerGenerator property, which holds an ItemContainerGenerator object that you can use to get item containers for items and item indexes and the like.
E.g.
var container = listView.ItemContainerGenerator.ContainerFromItem(item);
In UWP, the ContainerFromItem and similar methods are also directly available in the ItemsControl class, so that you could write
var container = listView.ContainerFromItem(item);
So I'm trying to use David Veeneman's Bindable WPF RichTextBox here in my .net 4.5 project. After adding the control and the ValueConverter in my code I noticed only the the public object Convert() will be triggered but the public object ConvertBack() not.
After reading the comments to this project I changed following parts of the control source code.
private static void OnDocumentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var thisControl = (EcoRichTextBox)d;
if (thisControl.m_InternalUpdatePending > 0)
{
thisControl.m_InternalUpdatePending--;
return;
}
// Changed:
try
{
thisControl.TextBox.Document = (e.NewValue == null) ? new FlowDocument() : (FlowDocument)e.NewValue;
}
catch { }
thisControl.m_TextHasChanged = false;
}
And this Event Handler:
private void OnTextChanged(object sender, TextChangedEventArgs e)
{
// Set the TextChanged flag
m_TextHasChanged = true;
// Changed:
Document = TextBox.Document;
}
Now the the both method of the ValueConverter worked fine but events like private void OnNormalTextClick(object sender, RoutedEventArgs e) causes a FatalExecutionEngineError on Runtime.
So i wonder if there are major changes form WPF 3.5 to 4.5?
Or anybody have an idea to work around this?
Update
Binding in XAML
<uc:FsRichTextBox Margin="5"
Document="{Binding Path=Ereignis.Bericht,
Converter={StaticResource flowDocumentConverter},
UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}" />
I ran the demo you linked here in VS2015 with target framework 4.0 and 4.5. It will not update when I take out the two way data binding.
Add to your RTB. Two way data binding and a name:
Mode=TwoWay
x:Name="EditBox"
I think rather than managing the text change yourself here, remove this:
// Changed:
Document = TextBox.Document;
Use an event handler to update the data.
Then in your event handler that is managing your updates (I am assuming a button click? And allow this to manage the update.
this.EditBox.UpdateDocumentBindings();
The x:name attribute is valuable.
This is all found in the source code.
If you can be more clear about how your project is arranged I can provide more detail. But for starters, I would do this. Stick more closely to the provided example.
I've created a custom usercontrol that's composed of a AutoCompleteBox with a Selected Item... till now I've implemented it in a way I don't like... I mean I've a XAML view, a Viewmodel and in the viewmodel I load data from a stored procedure.
Since the AutoComplete box is a third party UserControl I've added it to the XAML view and not defined as a custom usercontrol. What's the best practice to do so?
I think the fact that I'm using Catel as MVVM Framework is irrilevant right now..
Thanks
UPDATE #1
My usercontrols need to have some properties that are passed via XAML for example (LoadDefaultValue)
<views:PortfolioChooserView x:Name="PortfolioChooserView" DataContext="{Binding Model.PortfolioModel}" Height="25" LoadDefaultValue="True" Width="150" />
To achieve such a scenario I had to define a dependency property in my PortfolioChooserView defined as
public bool LoadDefaultValue
{
get { return (bool)GetValue(LoadDefaultValueProperty); }
set { SetValue(LoadDefaultValueProperty, value); }
}
public static readonly DependencyProperty LoadDefaultValueProperty = DependencyProperty.Register(
"LoadDefaultValue", typeof(bool), typeof(PortfolioChooserView), new PropertyMetadata(default(bool)));
Since if I would have defined it in Viewmodel only I wouldn't have been able to set it.
The odd thing is that in order to pass it to the viewmodel I had to do such a trick
public PortfolioChooserView()
{
InitializeComponent();
if (!isFirstLoad) return;
Focusable = true;
PortfolioCompleteBox.AllowDrop = true;
PortfolioCompleteBox.Focus();
DragDropManager.AddPreviewDragOverHandler(PortfolioCompleteBox, OnElementDragOver);
DragDropManager.AddDropHandler(PortfolioCompleteBox, OnElementDrop);
DataContextChanged += PortfolioChooserView_DataContextChanged;
isFirstLoad = false;
}
void PortfolioChooserView_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var dataContext = DataContext as PortfolioModel;
if (dataContext != null)
{
dataContext.LoadDefaultValue = LoadDefaultValue;
dataContext.AllowNull = AllowNull;
//var converter = new PortfolioConverter();
//var portfolio = (Portfolio) converter.Convert(SelectedItem, null, null, CultureInfo.CurrentCulture);
//dataContext.SelectedItem = portfolio;
}
}
But I really dislike to use the DataContextChanged event ...do you see a better approach?
Thank
UPDATE#2
I keep this toghether since It's a related question...
On some viewmodel I used DeferValidationUntilFirstSaveCall = true; in the Constructor to disable the validation at load but my custom usercontrols shows the red border around... what should I do to propagate that info to the nested usercontrols?
Thanks again
See Orc.Controls for tons of examples. It's an open-source library that has a lot of user controls built with Catel, even one with an auto complete box.
I have a WinForm UserControl inside a WPF window and the WPF code is using the MVVM pattern.
What is the best way to successfully integrate the WinForm control into the MVVM pattern?
Can I use some form of binding from the WPF side?
Let's say that I want to handle some events from the WF control, is there a way to fully go MVVM?
Thanks.
Note that this doesn't really answer the questions (I should have read better). If you're interested in using a WPF control in a WinForms app, here's an approach. My scenario is:
1) Have a WinForms control that is used many places in my app.
2) Want to develop a WPF implementation that will use the MVVM pattern.
3) Want to write the control as a proper WPF control complete with dependency properties so it can be used properly when my app is eventually all WPF.
4) Want to keep the same WinForms control and API to not break existing client code in my app.
Most everything was straightforward except for having my WinForms control raise events when properties of my WPF control changed. I wanted to use a binding but since the source of a binding must be a DependencyObject and a System.Windows.Forms.UserControl is not, I had to make a simple nested class. I wrote my WPF control exactly as if I was integrating it into a WPF application, and just did some extra thunking to get my WinForms wrapper to work.
Here's code for my WPF control:
public partial class MonkeySelector : UserControl
{
public static readonly DependencyProperty SelectedMonkeyProperty =
DependencyProperty.Register(
"SelectedMonkey", typeof(IMonkey),
typeof(MonkeySelector));
public MonkeySelector()
{
InitializeComponent();
}
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
// Note: No code is shown for binding the SelectedMonkey dependency property
// with the ViewModel's SelectedMonkey property. This is done by creating
// a Binding object with a source of ViewModel (Path = SelectedMonkey) and
// target of the SelectedMonkey dependency property. In my case, my
// ViewModel was a resource declared in XAML and accessed using the
// FindResource method.
}
public IMonkey SelectedMonkey
{
get { return (IMonkey)GetValue(SelectedMonkeyProperty); }
set { SetValue(SelectedMonkeyProperty, value); }
}
}
Here's the code for my WinForms control:
public partial class WinFormsMonkeySelector : UserControl
{
public event EventHandler SelectedMonkeyChanged;
private MonkeySelector _monkeySelector;
private WpfThunker _thunker;
public WinFormsMonkeySelector()
{
InitializeComponent();
_monkeySelector = new MonkeySelector();
_elementHost.Child = _monkeySelector;
System.Windows.Data.Binding binding = new System.Windows.Data.Binding("SelectedMonkey");
binding.Source = _monkeySelector;
binding.Mode = System.Windows.Data.BindingMode.OneWay;
_thunker = new WpfThunker(this);
// Note: The second parameter here is arbitray since we do not actually
// use it in the thunker. It cannot be null though. We could declare
// a DP in the thunker and bind to that, but that isn't buying us anything.
System.Windows.Data.BindingOperations.SetBinding(
_thunker,
MonkeySelector.SelectedMonkeyProperty,
binding);
}
protected virtual void OnSelectedMonkeyChanged()
{
if (SelectedMonkeyChanged != null)
SelectedMonkeyChanged(this, EventArgs.Empty);
}
public IMonkey SelectedMonkey
{
get { return _monkeySelector.SelectedMonkey; }
set { _monkeySelector.SelectedMonkey = value; }
}
private class WpfThunker : System.Windows.DependencyObject
{
private WinFormsMonkeySelector _parent;
public WpfThunker(WinFormsMonkeySelector parent)
{
_parent = parent;
}
protected override void OnPropertyChanged(System.Windows.DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
// Only need to check the property here if we are binding to multiple
// properties.
if (e.Property == MonkeySelector.SelectedMonkeyProperty)
_parent.OnSelectedMonkeyChanged();
}
}
}
Personally, I would handle this by creating a WPF UserControl that wraps the Windows Forms control. This would allow you to encapsulate all of the required code-behind into your WPF Control, and then use it in a pure MVVM manner.
It will be difficult to stay "pure" MVVM using a Windows Forms control directly, as Windows Forms controls typically require a different binding model, as well as typically requiring direct event handling.
You might have a look at the WAF Windows Forms Adapter. It shows a possible way to use Windows Forms together with MVVM.
My application has to support multiple languages and should be able to switch the language on run time. For that purpose I am using LocalizationExtension from codeplex (http://wpflocalizeextension.codeplex.com/) I am using Ribbon Contorl in my application. I am creating ribbonCommands as window resource and Binding the LableTitle and other properties withLocalizationExtension class.
<MvvmCore:RibbonCommandExtended x:Key="SwitchLanguageCommand"
CanExecute="RibbonCommandExtended_CanExecute"
Executed="RibbonCommandExtended_Executed"
LabelTitle="{lex:LocText Key=SwitchLanguage,Dict=LanRes}"
ToolTipTitle="{lex:LocText Key=SwitchLanguage,Dict=LanRes}"
LargeImageSource="{lex:LocImage Key=ChangeLanguage,Dict=LanRes}"/>
Then assigning it to button Command property as static resource.
<rb:RibbonButton x:Name="EnglishButton" Command="{StaticResource SwitchToEnglishCommand}" Click="EnglishButton_Click">
Here is my RibbonCommandExtended class.
public class RibbonCommandExtended : RibbonCommand
{
private ICommand m_command;
public ICommand Command
{
get { return m_command; }
set
{
m_command = value;
if (m_command != null)
{
this.CanExecute += UsCanExecute;
this.Executed += UsExecuted;
}
else
{
this.CanExecute -= UsCanExecute;
this.Executed -= UsExecuted;
}
}
}
private void UsExecuted(object sender, ExecutedRoutedEventArgs e)
{
Command.Execute(e.Parameter);
}
private void UsCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = Command.CanExecute(e.Parameter);
}
}
When my program starts, then the ribbon control picks the right language strings and images. But when I change the language on runtime then I could't see any change in ribbon control localized text and image. Because the RibbonCommand's LabelTitle, LargeImageSource and all other properties are not Dependency properties.
Have someone solved the issue already? Or is there any other way rather then LocalizationExtension to make my application localized so that it fulfills my requirements?
It is easy to use the LocalizationExtension to localize the application. But perhaps, we should back to the basic method to do the localization, separated culture resource and the change it on the run-time. Please refer to the http://msdn.microsoft.com/en-us/library/ms788718.aspx. You may need the Locbaml tool to generate the CVS that we can edit it for several culture, and then load the CSV to the resource dll file for different culture, and change it by the code:
Thread.CurrentThread.CurrentCulture = new CultureInfo(...);
The following project provides a guidance about WPF localization - WPF Localization Guidance Whitepaper may help you: http://wpflocalization.codeplex.com/