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;
}
}
}
Related
I have a class which extends Canvas in WPF.
This class is placed in a ScrollViewer.
Without passing a specific reference to the ScrollViewer into the Canvas, I want to find the ScrollViewer which contains the Canvas from within the Canvas itself.
The Parent property of the class which extends Canvas is null, and every attempt to use the VisualTreeHelper just returns null as well.
I have attempted to find the visual ancestor using VisualTreeHelper.GetParent(this), however the parent property is null.
As the ExtendedCanvas will be used in multiple instances, I would like it to be able to resolve its containing ScrollViewer without the need to specifically reference the ScrollViewer in either code behind or in XAML.
I realise that I could add a dependency propery in the ExtendedCanvas and create a binding in the XAML, however I would like the component to work by simply dropping it into a container.
Similarly, I would not be averse to placing the ScrollViewer onto a panel of some sort, then placing my ExtendedCanvas within it, so that my component uses that panel as its lowermost containing element.
What is puzzling me is that as I understand it, the VisualTreeHelper will navigate the entire visual tree for the running application. It seems that either my assumption is entirely wrong, or it can only navigate downwards from the specified component.
Is this possible to achieve without the above approaches?
Example Code:
cs -
public class ExtendedCanvas:Canvas {
//I wish to automatically populate this scroll viewer
//reference to the instance of the scrollviwer which contains
//this ExtendedCanvas instance
private ScrollViewer _containingScrollViewer = null;
}
xaml -
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" >
<local:ExtendedCanvas x:Name="extendedCanvas" />
</ScrollViewer>
You can find its parent like this:
public ExtendedCanvas()
{
//it hasn't been added to its parent yet
Loaded += ExtendedCanvas_Loaded;
}
private void ExtendedCanvas_Loaded(object sender, RoutedEventArgs e)
{
//now it is added to its parent
_containingScrollViewer = Parent as ScrollViewer;
}
I am using MVVM Light WPF 4.
I have a ContentPresenter in my Home.xaml.
<ContentPresenter Name="MDI" Content="{Binding WindowName, Mode=OneWay}">
I am binding user control to this in viewmodel like
public UserControl WindowName { get; set; }
void ShowSalesEntry()
{
WindowName = null;
WindowName = new SalesEntry();
RaisePropertyChanged("WindowName");
}
by using command in a menu click and it is binding fine.
Now in the user control i have a button which i used to close (but to close i change the visibility
to collapsed) by this way..
Visibility="{Binding visibility, Mode=OneWay}"
in the user control view model,
public SalesEntryViewModel()
{
visibility = Visibility.Visible;
cmdExitWindow = new RelayCommand(ExitWindow);
RaisePropertyChanged("visibility");
}
and the following to close (visibility to collapsed)
public RelayCommand cmdExitWindow { get; set; }
void ExitWindow()
{
visibility = Visibility.Hidden;
RaisePropertyChanged("visibility");
}
To exit (means visibility collapsed)..
This is working fine upto this.
Problem is when i click the same page i mean to show the same user control,
now this time the visibility is still collapsed. Even though i changed to visible in the
load event.
How to solve this..
I am new to MVVM WPF.. Please help me..
Problem is when i click the same page i mean to show the same user
control, now this time the visibility is still collapsed. Even though
i changed to visible in the load event.
Based on this comment and the code provided, you've either omitted code, or you've confused the purpose of the constructor.
In your constructor, you have set the Visibility to Visible. You then have a method that sets the Visibility to Hidden, but there is nothing to ever set it back to Visible once this has occurred. The constructor only fires when the object is created. You need something to set the Visibility back at the appropriate time (ie. your comment "when i click the same page").
//Add these lines to the method/event that will show the control again
visibility = Visibility.Visible;
RaisePropertyChanged("visibility");
That's the best answer I can give based on what you've provided.
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);
}
I have a user interface with a TabControl that initially displays a start page. Other items can be added to it by double-clicking on content in, for example, a DataGrid. New tabs should be selected when they are created. If the document corresponding to the item in the grid is already open, then the existing tab for that document should be opened rather than creating a new one.
I know that I should be able to programmatically select a tab by setting the TabControl's SelectedItem or SelectedIndex properties. However, the desired tab never actually activates. If I set one and then inspect the TabControl's state in the debugger, then both fields seem to update properly. However, after I continue execution, I see that the selected tab remains unchanged in the UI, and if I pause and inspect the TabControl's state again I see that the SelectedItem and SelectedIndex have returned to their previous values. Selecting a tab by clicking on it in the UI, on the other hand, works just fine.
Here's the declaration for the TabControl:
<TabControl x:Name="Tabs" >
<TabItem x:Name="StartPageTab" Header="Start Page" DataContext="{Binding Path=StartPageViewModel}">
...
</TabItem>
</TabControl>
And the code for adding and selecting tabs:
private void _SelectTab(MyViewModel model)
{
TabItem tab;
if (_TryFindTab(model, out tab)) Tabs.SelectedItem = tab;
}
private bool _TryFindTab(MyViewModel target, out TabItem tab)
{
foreach (TabItem item in Tabs.Items)
{
MyViewModel model = item.DataContext as MyViewModel;
if (model != null && model.Equals(target))
{
tab = item;
return true;
}
}
tab = null;
return false;
}
private void _AddTab(MyViewModel model)
{
TabItem tab = new TabItem { DataContext = model, Content = new MyView() };
Binding bind = new Binding { Source = model, Path = new PropertyPath("Name") };
tab.SetBinding(TabItem.HeaderProperty, bind);
Tabs.Items.Add(tab);
Tabs.SelectedItem = tab;
}
It turned out to be related to something I conveniently omitted from the original problem description:
The DataGrid in question was in the content for StartPageTab. I was handling double-clicks on that DataGrid by capturing its MouseDoubleClick event, searching the visual tree to find what DataGridRow was double-clicked (if any), and then raising an event that would eventually be captured by the main window, which would respond by calling either _SelectTab or _AddTab, depending on whether the document in question was already open.
At which point, the call stack would unroll and get back to that MouseDoubleClick event handler. In that handler, I forgot to set the MouseButtonEventArgs's Handled property to true. So WPF kept searching for someone else to handle that click event - and the element that it eventually found would respond by asking for focus, which in turn meant that the original tab needed to get focus back.
Adding e.Handled = true; stopped that whole mess in its tracks, so the new tab could stay selected.
You could try using tab.focus()
I have tabs in my application and this is a quick way to make your selected tab visible.
Have you tried binding to TabItem.IsSelected and updating that in you view model?
In an older C# app I had, using page controls, I was able to force the page active by telling the tab control object to select the tab...
MyTabControlWithMultiplePages.SelectTab(PageIWantShown);
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?