Turning off Virtualization for Tabcontrol without itemsource - WPF - c#

I have a bug with Tabcontrols and Oxyplot. The Structure of my xaml is like this: I have an AvalonDock document and inside I have 3 harcoded tabs for each document. One of those tabs has another tabcontrol with an Oxyplot View inside each tab. Problem is when I open two (sometimes three) Avalondock Documents, I get the following exception:
This PlotModel is already in use by some other PlotView control.
I guess it is because the tabcontrol is virtualizing the tabs, and the plot model is being used several times for different views. How can I prevent it?
Problem is very similar to this one,
http://discussion.oxyplot.org/topic/506228-error-this-plotmodel-is-already-in-use-by-some-other-plotview-control/
but I don't think it doesn't have solution yet. I tried the virtualization-off solution given here
http://www.codeproject.com/Articles/460989/WPF-TabControl-Turning-Off-Tab-Virtualization
and worked properly, but that was for tabs from a template and Not from hardcoded tabs.
Any ideas?
Thanks
Regards.
Saul Hidalgo.

You can use the following code to remove view from PlotModel
private PlotVm vm = new PlotVm();
private void LayoutRoot_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
var window = new PlotWindow();
((IPlotModel)vm.Model)?.AttachPlotView(null);
window.DataContext = vm;
Debug.WriteLine(vm.Model.PlotView);
window.ShowDialog();
Debug.WriteLine(vm.Model.PlotView);
}

Related

Add WebView2 control to tabitem and navigate to website

I'm fiddling around with Microsofts WebView2 control. I created a simple wpf application, nothing fancy: Just MainWindow with a tabcontrol. Every tab has it's own user data directory.
Then I added a method to instantiate a tabitem and a webview and then add that control to the tabcontrol in MainWindow.
What I have so far - which is working (but...):
private async void AddTab(string serviceName, string serviceUrl)
{
TabItem tabItem = new TabItem();
tabItem.Name = serviceName;
tabItem.Header = serviceName;
myTabControl.Items.Add(tabItem);
// Create a unique user data directory for this tab
// and keep it "portable", a.k.a. in the programm directory
string userDataDirectory = System.IO.Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName) + "\\Sessions\\" + serviceName;
// Create a new WebView2 control and pass custom user data directory
WebView2 webView = new WebView2() { CreationProperties = new CoreWebView2CreationProperties() { UserDataFolder = userDataDirectory } };
// set the source address
webView.Source = new Uri(serviceUrl);
// add webview to tab content
tabItem.Content = webView;
// Initialize the WebView2 control
await webView.EnsureCoreWebView2Async();
}
This works as intended. But as it is now, the webview content is only loaded when I click on the tab. I'm not sure how to make the tabs load in the background, so that when I click on the tab, the website is already loaded.
I found one example on stackoverflow, but it is too complex for what I want to accomplish.
My experiment with a Task brought me into the crazy world of not being able to access my thread again. Such code is way over my head (for now). I'm still learning.
I would try defining the tabitems in xaml.
The tabcontrol is an itemscontrol. I would usually bind the itemssource to a collection of viewmodels and template those out into ui. You only get whichever one of those items is selected instantiated if you bind the items like that. Defining tabitems in xaml seems to mean they're all instantiated. It's noticeably slower.
You can add other tabs afterwards in code if you wanted. But I'd check what happens with some defined in xaml first.
Another thing you could try is hidden webview2 controls in the main view. Navigate to these fixed url immediately the window renders.
Move them out the window and into your tabitem on selecting that tab, set isvisible true. Then you're just incurring rendering.
Btw
The webview2 is a wrapper around a chromium edge browser. It displays some odd behaviour like it can overflow parent containers.

How to remove Windows border from WPF UserControl?

To preface this question, I am working on coding the back end of an application whose UI was put together by someone else (I believe using Blend). The application consists of a series of "Screens," whose root element in XAML is "UserControl". There is no use of the "Window" tag anywhere in the source.
What I want to do is remove the Windows border that is added to the outside edge of the application when I run the program. The border currently consists of forward/backward buttons like a web browser, and an X button to close.
All I can find from searches are instructions to add
WindowStyle="None"
to the
<Window>
element. But of course, I don't have one of those, and WindowStyle is not a property of UserControl. Anyone know how to accomplish this with UserControl root elements?
Edit: The StartupUri for the application is
this.StartupUri = new Uri(#"pack://application:,,,/WpfPrototype1.Screens;Component/Screen_1.xaml");
the file it points to does not have a Window tag.
Based on the comments above it seems your MainWindow is created dynamically somewhere, however you can use the Application class to get the applications MainWindow.
var mainWindow = Application.Current.MainWindow;
And you can then set your border style from there
Example:
private void RemoveBorder()
{
var mainWindow = Application.Current.MainWindow;
if (mainWindow != null)//should never be
{
mainWindow.WindowStyle = System.Windows.WindowStyle.None; // removes top bar (icon, title, close buttons etc)
mainWindow.AllowsTransparency = true; //removes the border around the outside
}
}

RibbonGroup causes its children lose their DataContext

I am developing a WPF project and right now I am working on the Ribbon area.
Inside of the Ribbon I have some controls such as RibbonComboBox, RibbonTextBox, etc.
I have several RibbonTab in my Ribbon so I set one view model for each RibbonTab as following:
<rb:Ribbon Name="Ribbon">
<rb:RibbonTab Header="Tab One" Name="tab1">
<rb:RibbonTab.DataContext>
<vm:TabOneViewModel />
</rb:RibbonTab.DataContext>
So, each control within those tabs inherits their respective view model.
When I run the application everything works as expected. The problem starts when I resize the container window to a very small size, after doing this: Every control loses their bindings since their DataContext is replaced by an object called DisconnectedItem.
I have done some research and found that this is due to the controls stop being part of the visual tree and for that reason the binding engine sets their DataContext to DisconnectedItem.
What can I do to prevent my controls from losing their DataContext?
EDIT:
I just found that the main problem comes from the RibbonGroup control which after collapsing causes its visual children lose their DataContext.
I usually add DataContext="{Binding Mode=OneTime}" line for all ribbon controls as a workaround, which fixes the issue completely. You won't be able to change DataContext more than once though.
I just found a workaround for the RibbonGroup control issue here
The solution I took is creating a new control which inherits from RibbonGroup control.
public class MyRibbonGroup : RibbonGroup
{
public MyRibbonGroup()
: base()
{
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
// Force the bindings to be restored after
// the ribbon group collapsed or expanded to a menu button.
if (e.Property == RibbonGroup.IsCollapsedProperty)
{
object objDataContext = this.DataContext;
this.DataContext = null;
this.DataContext = objDataContext;
}
}
}

Sizing issues while adding a .Net UserControl to a TabPage

I have a complex Windows Forms GUI program that has a lot of automated control generation and manipulation. One thing that I need to be able to do is add a custom UserControl to a newly instatiated TabPage. However, when my code does this I get automatic resizing events that cause the formatting to get ugly. Without detailing all of the different Containers that could possibly be involved, the basic issue is this:
At a certain point in the code I create a new tab page:
TabPage tempTabPage = new TabPage("A New Tab Page");
Then I set it to a certain size that I want it to maintain:
tempTabPage.Width = 1008;
tempTabPage.Height = 621;
Then I add it to a TabControl:
tabControl.TabPages.Add(tempTabPage);
Then I create a user control that I want to appear in the newly added TabPage:
CustomView customView = new CustomView("A new custom control");
Here is where the problem comes in. At this point both the tempTabPage and the customView are the same size with no padding or margin and they are the size I want them to be. I now try to add this new custom UserControl to the tab page like this:
tempTabPage.Controls.Add(customView);
When making this call the customView and it's children controls get resized to be larger and so parts of the customView are hidden.
Can anyone give me any direction on what to look for or what could be causing this kind of issue?
Thanks ahead of time.
The UserControl's "AutoScaleMode" property should be set to "None".
If you want the customView to fill the TabPage.
Use Dock like this:
tempTabPage.Controls.Add(customView);
customView.Dock = DockStyle.Fill;
Then the customView will fill out the space in the TabPage, but you have to handle resizing of the customView so child controls will be shown properly.
I had the same issue.
The UserControl's "AutoScaleMode" set to "None" works for me.

How to pass information from one WPF UserControl to another WPF UserControl?

I've got a WPF application.
On the left side there is a stackpanel full of buttons.
On the right side there is an empty dockpanel.
When user clicks a button, it loads the corresponding UserControl (View) into the dockpanel:
private void btnGeneralClick(object sender, System.Windows.RoutedEventArgs e)
{
PanelMainContent.Children.Clear();
Button button = (Button)e.OriginalSource;
Type type = this.GetType();
Assembly assembly = type.Assembly;
IBaseView userControl = UserControls[button.Tag.ToString()] as IBaseView;
userControl.SetDataContext();
PanelMainContent.Children.Add(userControl as UserControl);
}
This pattern works well since each UserControl is a View which has a ViewModel class which feeds it information which it gets from the Model, so the user can click from page to page and each page can carry out isolated functionality, such as editing all customers, saving to the database, etc.
Problem:
However, now, on one of these pages I want to have a ListBox with a list of Customers in it, and each customer has an "edit" button, and when that edit button is clicked, I want to fill the DockPanel with the EditSingleCustomer UserControl and pass it the Customer that it needs to edit.
I can load the EditCustomer usercontrol, but how do I pass it the customer to edit and set up its DataContext to edit that customer?
I can't pass it in the constructor since all the UserControls are already created and exist in a Dictionary in the MainWindow.xaml.cs.
so I created a PrepareUserControl method on each UserControl and pass the Customer to it and can display it with a textbox from code behind with x:Name="..." but that is not the point, I need to DataBind an ItemsControl to a ObservableCollection to take advantage of WPF's databinding functionality of course.
so I tried to bind the ListBox ItemSource in the View to its code behind like this:
<UserControl.Resources>
<local:ManageSingleCustomer x:Key="CustomersDataProvider"/>
</UserControl.Resources>
<DockPanel>
<ListBox ItemsSource="{Binding Path=CurrentCustomersBeingEdited, Source={StaticResource CustomersDataProvider}}"
ItemTemplate="{DynamicResource allCustomersDataTemplate}"
Style="{DynamicResource allCustomersListBox}">
</ListBox>
</DockPanel>
which gets a stackoverflow error caused by an endless loop in the IntializeComponent() in that view. So I'm thinking I'm going about this in the wrong way, there must be some easier paradigm to simply pass commands from one UserControl to another UserControl in WPF (and before someone says "use WPF commanding", I already am using commanding on my UserControl that allows the user to edit all customers, which works fine, but I have to handle it in my code behind of my view (instead of in my viewmodel) since I need the parent window context to be able to load another user control when its finished saving:
<Button Style="{StaticResource formButton}"
Content="Save"
Command="local:Commands.SaveCustomer"
CommandParameter="{Binding}"/>
private void OnSave(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
{
Customer customer = e.Parameter as Customer;
Customer.Save(customer);
MainWindow parentShell = Window.GetWindow(this) as MainWindow;
Button btnCustomers = parentShell.FindName("btnCustomers") as Button;
btnCustomers.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
}
So how in WPF can I simply have a UserControl loaded in a DockPanel, inside that UserControl a button with a command on it that loads another UserControl and sends that UserControl a specific object to which it can bind its controls?
I can imagine I just do not know enough about WPF commands at this point, if anyone can point me in the right direction from here, that would be great, or if you think this "loading UserControls in a DockPanel pattern is foreign to WPF and should be avoided and replaced with another way to structure applications", that would be helpful news as well. You can download the current state of my application here to get an idea of how it is structured. Thanks.
I've just finished a LOB application using WPF where this sort of problem/pattern appeared constantly, so here's how I would have solved your problem:
1) In the DataTemplate where you create each item in the ListBox, along with it's edit button, bind the Button's tag property to the Customer object underlying that list box item.
2) Create a Click event handler for the button, and set the Button's Click event to fire the handler.
3) In the event handler, set the Content property of the UserControl.
4) Set up a DataTemplate in scope of the User Control (perhaps in the resources of it's immediate container) which describes an editor for that single customer.
Another approach that will work is to declare a Customer dependency property on your EditCustomer class, then set that property (perhaps through a XAML Trigger) when the button is clicked.
I hope this isn't too vague. If nothing else, know that the problem you're facing is very solvable in WPF.
This is where you use the Mediator pattern. There's several blog posts on this topic (for instance), and there's implementations of the pattern in some WPF frameworks (such as EventAggregator in Prism).
I don't have the time to really dig into this (it's an interesting question and I hope you get a good answer-- I can see myself running into a similar situation in the future).
Have you considered getting a little less WPF-y and falling back to firing an event on your source UserControl with an EventArgs that contains the customer, then in the event handler, firing the appropriate command on the target control?

Categories

Resources