Can Converter know about parent control? - wpf

I have a Prism Core MVVM application, which reuses some views, like CustomerView. Initially, CustomerView is shown as a 'tile' in the MainWindowView. When user clicks on it - new instance of that CustomerView is opened in a new Window, using my WindowService. CustomerView has a menu. What I want: if CustomerView is displayed in MainWindowView's 'tile' - menu should be hidden; if in another distinct Window - menu should be visible. Currently, I have this done with code-behind. Is it possible to have a Converter, which could figure if CustomerView is a part of MainWindowView, or of some other Window?

You could bind to the CustomerView itself and use a helper method that tries to find a parent MainWindowView in the visual tree:
public static T FindParent<T>(DependencyObject dependencyObject) where T : DependencyObject
{
var parent = VisualTreeHelper.GetParent(dependencyObject);
if (parent == null) return null;
var parentT = parent as T;
return parentT ?? FindParent<T>(parent);
}
Usage:
MainWindowView parent = FindParent<MainWindowView>(customerView);
if (parent != null)
//MainWindowView found...

Related

How to rebuild logical tree in WPF without showing window

I have a test code that add a TabItem to TabControl.
But when I try to find the TabItem by name, null is return.
I found a solution is show the window, then I can find the TabItem by name.
But when many tests are running, OutOfMemory exception is occurred because many windows are opened.
Is there another solution to rebuild logical tree without showing window?
The following is my test code
[TestMethod]
public void MyTest2()
{
// Arrange
// Initilize a subVM of CMSEditorViewModel type
var subVM = new SubViewModel();
// Initialize a mainVM of CMSEditorMainViewModel type
var mainVM = new MainViewModel();
// Initialize a MainWindow of DynamicCMS.Exe.CMSEditor
var mainWindow = new MyEditor.MainWindow();
mainWindow.DataContext = mainVM;
ContentPresenter presenter = new ContentPresenter();
using (var stream = System.IO.File.OpenRead(CmsPath.DirViewWithBS + "Subscreen.xaml"))
{
DataTemplate template = XamlReader.Load(stream) as DataTemplate;
presenter.ContentTemplate = template;
presenter.Content = subVM;
}
// Create a TabItem of TabControl
TabItem item = new TabItem();
item.Header = "Tab1";
item.Content = presenter;
item.Name = "tab1";
// Get "mainTabControl" TabControl from MainWindow
CustomTabControl tab = CmsUtil.GetControl((Visual)mainWindow.Content, "mainTabControl") as CustomTabControl;
// Add TabItem to TabControl
tab.Items.Add(item);
mainWindow.Show() // After showing window, I can find the TabItemControl
// Act
TabItem tabItem = (TabItem)CmsUtil.GetControl((Visual)mainWindow.Content, "tab1");
// Assert
Assert.IsNotNull(tabItem);
}

Window FontSize is not inherited in child control

In certain cases I have the problem that the FontSize that I set on a WPF window is not inherited to a child control.
It happens, if a custom user control sets its content (e.g. a Label) upon changing the DataContext.
I can reproduce this when putting this UserControl into a new window, then close this window and create a new one having the same UserControl in it (see the following code).
In my complex application it's a custom popup window and a custom UserControl that changes its content if the DataContext is changed. There the font is not inherited upon the first open of the window (so the usercontrol hasn't been in another visual/logical tree until that) but I can't reproduce this in a small test application.
public partial class App : Application
{
// App.xaml: ShutdownMode="OnExplicitShutdown"
private void Application_Startup(object sender, StartupEventArgs e)
{
var testControl = new TestControl();
var w = new Window();
w.FontSize = 40;
w.DataContext = this;
w.Content = testControl; // TestControl.DataContextChanged creates label which has FontSize = 40
w.Show();
w.Close();
w.DataContext = null;
//w.Content = null; // if this is done, the font will be correct (40)
w = null;
w = new Window();
w.FontSize = 40;
w.DataContext = this;
//testControl.DataContext = this; // if this is done, the font will be correct (40)
w.Content = testControl; // TestControl.DataContextChanged creates label with remaining FontSize = 12 (Default)
w.Show();
}
}
public class TestControl : UserControl
{
public TestControl()
{
DataContextChanged += TestControl_DataContextChanged;
}
private void TestControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != null) Content = new Label() { Content = "TestControllabel"};
else Content = null;
}
}
I'm not looking for a fix of this example app, but for the reason why the font size is not inherited in this special case, so maybe then I can fix my complex app.
Any thoughts would be useful !
Edit: For now I fixed my application by setting the datacontext of the control before setting it as window content.

How to add tab items to existing tab control from user Control in wpf

I have MainWindow and 2 user controls. In Main Window there is Tab Control which loads User Control if you click on button search in MainWindow. I could add tab items in main Window by this code.
private void search(object sender, RoutedEventArgs e)
{
tc.Visibility = Visibility.Visible; // It is hidden by default
TabItem tab = new TabItem();
tab.Header = "Поиск";
UCSearch c = new UCSearch(); // User Control 1
tab.Content = c;
tc.Items.Add(tab);
}
When User Control 1 is loaded in Tab item. There is Tetxbox and Button in User Control 1.I want to load User Control 2 when is clicking to Button. But I can not get access to Tab Control which is in Main Window from User Control 1. Please Give me direction. Where to dig?
You could use an Extension method to search the VisualTree for a Parent of type TabControl.
e.g.
Extension method:
public static class VisualTreeExtensions
{
public static T FindParent<T>(this DependencyObject child)
where T : DependencyObject
{
//get parent item
DependencyObject parentObject = VisualTreeHelper.GetParent(child);
//we've reached the end of the tree
if (parentObject == null) return null;
//check if the parent matches the type we're looking for
var parent = parentObject as T;
if (parent != null)
{
return parent;
}
else
{
return FindParent<T>(parentObject);
}
}
In your Button Handler:
private void Button_Click(object sender, RoutedEventArgs e)
{
var tabControl = (sender as Button).FindParent<TabControl>();
tabControl.Items.Add(new TabItem() { Header = "New"});
}
The better and more flexible (but also more complicated) solution would be to notify the participants (here: your Button fires some kind of message that it was clicked, others (your TabControl) listen and react on it (create a new Tab).
This can for example be done with a Mediator pattern or an EventAggregator.

Get the ComboBox that a ComboBoxItem resides in

I need to find the ComboBox that a ComboBoxItem resides in.
In codebehind I catch an event when a ComboBoxItem is clicked, but I don't know which one of several ComboBoxes that the specific ComboBoxItem belongs to. How do I find the ComboBox?
Normally you can use LogicalTreeHelper.GetParent() and traverse up the logical tree from the ComboBoxItem to find the ComboBox. But this only works if the ComboBoxItems are added to the ComboBox manually, not when the items are applied to the ComboBox with databinding. When using databinding, the ComboBoxItems do not have the ComboBox as a logical parent (I don't understand why).
Any ideas?
More info:
Below is some code reconstructing my problem (not my actual code). If I would change from databinding the ComboBoxItems to setting them manually (in the XAML), the variable "comboBox" would be set to the correct ComboBox. Now comboBox is only null.
XAML:
<ComboBox Name="MyComboBox" ItemsSource="{Binding Path=ComboBoxItems, Mode=OneTime}" />
CodeBehind:
public MainWindow()
{
InitializeComponent();
MyComboBox.DataContext = this;
this.PreviewMouseDown += MainWindow_MouseDown;
}
public BindingList<string> ComboBoxItems
{
get
{
BindingList<string> items = new BindingList<string>();
items.Add("Item E");
items.Add("Item F");
items.Add("Item G");
items.Add("Item H");
return items;
}
}
private void MainWindow_MouseDown(object sender, MouseButtonEventArgs e)
{
DependencyObject clickedObject = e.OriginalSource as DependencyObject;
ComboBoxItem comboBoxItem = FindVisualParent<ComboBoxItem>(clickedObject);
if (comboBoxItem != null)
{
ComboBox comboBox = FindLogicalParent<ComboBox>(comboBoxItem);
}
}
//Tries to find visual parent of the specified type.
private static T FindVisualParent<T>(DependencyObject childElement) where T : DependencyObject
{
DependencyObject parent = VisualTreeHelper.GetParent(childElement);
T parentAsT = parent as T;
if (parent == null)
{
return null;
}
else if (parentAsT != null)
{
return parentAsT;
}
return FindVisualParent<T>(parent);
}
//Tries to find logical parent of the specified type.
private static T FindLogicalParent<T>(DependencyObject childElement) where T : DependencyObject
{
DependencyObject parent = LogicalTreeHelper.GetParent(childElement);
T parentAsT = parent as T;
if (parent == null)
{
return null;
}
else if(parentAsT != null)
{
return parentAsT;
}
return FindLogicalParent<T>(parent);
}
This is probably what you are looking for:
var comboBox = ItemsControl.ItemsControlFromItemContainer(comboBoxItem) as ComboBox;
I love how descriptive that method-name is.
On a side-note, there are some other useful methods which can be found in the property ItemsControl.ItemContainerGenerator which let you get the container associated with the templated data and vice versa.
On another side-note, you usually should not be using any of them and instead use data-binding actually.

WPF - Animate ListBox.ScrollViewer.HorizontalOffset?

I have a collection of Visuals in a ListBox. I need to find the XPosition of an element inside it and then animate the HorizontalOffset of the ListBox's ScrollViewer. Essentially I want to created an animated ScrollIntoView method.
This gives me a couple of problems. Firstly, how can I get a reference to the ListBoxs scrollviewer? Secondly, how can i get the relative XPosition or HozintalOfffset of an arbitrary element in the ListBox?
I'm not reponding to any input on the ListBox itself so I can't use Mouse related properties.
I don't think you will be able to use a WPF storyboard for the animation because storyboards animate WPF dependency properties. You will need to call ScrollViewer.ScrollToHorizontalOffset(double) to scroll.
You could try creating a custom dependency property that calls SetHorizontalOffset in the OnDependencyPropertyChanged() function. Then you could animate this property.
public static readonly DependencyProperty ScrollOffsetProperty =
DependencyProperty.Register("ScrollOffset", typeof(double), typeof(YOUR_TYPE),
new FrameworkPropertyMetadata(0.0, new PropertyChangedCallback(OnScrollOffsetChanged)));
public double ScrollOffset
{
get { return (double)GetValue(ScrollOffsetProperty); }
set { SetValue(ScrollOffsetProperty, value); }
}
private static void OnScrollOffsetChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
YOUR_TYPE myObj = obj as YOUR_TYPE;
if (myObj != null)
myObj.SCROLL_VIEWER.ScrollToHorizontalOffset(myObj.ScrollOffset);
}
To get the scroll viewer you can use the VisualTreeHelper to search the visual children of the ListBox. Save a reference to the ScrollViewer because you will need it later. Try this:
public static childItem FindVisualChild<childItem>(DependencyObject obj)
where childItem : DependencyObject
{
// Iterate through all immediate children
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is childItem)
return (childItem)child;
else
{
childItem childOfChild = FindVisualChild<childItem>(child);
if (childOfChild != null)
return childOfChild;
}
}
return null;
}
This function returns the first visual child of the parameter type. Call FindVisualChild<ScrollViewer>(ListBox) to get the ScrollViewer.
Finally, try using UIElement.TranslatePoint(Point, UIElement) to get the X position of the item. Call this function on the item, pass in 0,0 for the point, and pass in the ScrollViewer.
Hope this helps.
I'm not sure if my method is good practice but for the limited time I had it seemed to work okay. Instead of using a story board I just used a DispatcherTimer instead.
ScrollLeftButtonCommand = new DelegateCommand(
o =>
{
var scrollViewer = (ScrollViewer)o;
scrollTimer = new DispatcherTimer();
scrollTimer.Start();
scrollTimer.Interval = TimeSpan.FromMilliseconds(30);
scrollTimer.Tick += (s, e) =>
{
scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset - 50);
if (scrollViewer.HorizontalOffset <= 0)
{
scrollTimer.Stop();
}
};
});
Make sure it's a DispatchTimer so the thread is able to take control of the UI element
Also remember to bind to your object in your view!
<Button CommandParameter="{Binding ElementName=MyScrollViewer }"
Command="{Binding ScrollLeftButtonCommand }"/>

Resources