List all controls of a specific window of WPF - wpf

I am very new to WPF and just would to ask your help for a very basic method of getting the Windows controls and their children as in Winform app. Bottom line is to have reusable code for multiple window/pages in a different different class.
Bunches of thanks before.
Public Sub GetControl(Wn As Window)
For Each Ctrl As Control In Wn.Controls
'Code here
If Ctrl.HasChildren = True Then
'Code here
End If
Next
End Sub

So here's the down low. You need to look for UIElement, which is a base class for all UIElements in XAML. There are two main types that host controls. ContentControl and Panel.
A ContentControl has a 'Content' property that is potentially containing an object.
A Panel has a collection of UIElements property 'Children' and of type UIElement.
If you're looking just for the elements of a Window or ANY UIElement then you need to recursively search and make that list based on that information.
A Window inherits from ContentControl but that Content might be a Grid or StackPanel or any Panel or sorts and may have Children of UIElement also.
You cycle through them all until you get the results.
public MainWindow()
{
InitializeComponent();
foreach (var element in GetAllElementsFrom(this))
Debug.WriteLine(element.ToString());
}
private IEnumerable<UIElement> GetAllElementsFrom(UIElement element)
{
var uiElements = GetSingleElement();
switch (element)
{
case Panel panel:
foreach (var child in panel.Children)
uiElements = uiElements.Concat(GetInnerElements(child));
break;
case UserControl userControl:
uiElements = uiElements.Concat(GetInnerElements(userControl.Content));
break;
case ContentControl contentControl:
if (contentControl.Content is UIElement uiElement)
uiElements = uiElements.Concat(GetInnerElements(uiElement));
break;
}
return uiElements;
IEnumerable<UIElement> GetSingleElement()
{
yield return element;
}
}
Here's the XAML I used.
<Grid>
<Button>
<DockPanel>
<ContentControl>
<Grid>
<TextBox />
</Grid>
</ContentControl>
</DockPanel>
</Button>
<StackPanel>
<Label />
<TextBlock>
Hey There!!
</TextBlock>
<Grid>
<Ellipse />
</Grid>
</StackPanel>
</Grid>
And here's the result I got in my debug window:
System.Windows.Controls.Grid
System.Windows.Controls.Button
System.Windows.Controls.DockPanel
System.Windows.Controls.ContentControl
System.Windows.Controls.Grid
System.Windows.Controls.TextBox
System.Windows.Controls.StackPanel
System.Windows.Controls.Label
System.Windows.Controls.TextBlock
System.Windows.Controls.Grid
System.Windows.Shapes.Ellipse
Happy Coding! Note: Uses C# 7 syntax; if you're not on C# 7 then just make the changes, I think they're straight forward.

Related

Changing content changes DataContext when using DataTemplate

X problem.
I want to enlarge (let it take whole available space) a part of window content temporarily.
Window layout is pretty complicated: many nested panels, splitters, content to enlarge is 10 levels deep. Changing Visibility to stretch content is simply not enough (thanks to splitters) and seems very complicated.
Y problem
I decide to move that content into a user control and do something like (pseudo-code)
if(IsEnlarged)
{
oldContent = window.Content; // store
window.Content = newContent;
}
else
window.Content = oldContent; // restore
No problems. It was working perfectly in test project ... until I start using data templates.
Problem: if data templates are used then as soon as window.Content = newContent occurs, then that newContent.DataContext is lost and become the same as window.DataContext. This will trigger various binding errors, attached behaviors suddenly changes to default value, etc.. all kind of bad stuff.
Question: why DataContext is changing? How to prevent/fix this issue?
Here is a repro (sorry, can't make it any shorter):
MainWindow.xaml contains
<Window.Resources>
<DataTemplate DataType="{x:Type local:ViewModel}">
<local:UserControl1 />
</DataTemplate>
</Window.Resources>
<Grid Background="Gray">
<ContentControl Content="{Binding ViewModel}" />
</Grid>
MainWindow.cs contains
public ViewModel ViewModel { get; } = new ViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
UserControl1.xaml contains
<Button Width="100"
Height="100"
CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
Command="{Binding Command}" />
ViewModel (using DelegateCommand)
public class ViewModel
{
public DelegateCommand Command { get; set; }
bool _set;
object _content;
public ViewModel()
{
Command = new DelegateCommand(o =>
{
var button = (Button)o;
var window = Window.GetWindow(button);
_set = !_set;
if (_set)
{
_content = window.Content;
var a = button.DataContext; // a == ViewModel
window.Content = button;
var b = button.DataContext; // b == MainWindow ??? why???
}
else
window.Content = _content;
});
}
}
Set breakpoint on var a = ..., start program, click the button, do steps and observe button.DataContext as well as binding error in Output window.
ok here some general thoughts.
if you bind a object(viewmodel) to the Content property of the contentcontrol then wpf uses a DataTemplate to visualize the object. if you dont have a DataTemplate you just see object.ToString(). DataContext inheritance means that the DataContext is inherited to the child elements. so a real usercontrol will inherit the DataContext from the parent. the are common mistakes you find here on stackoverflow when creating UserControls - they often break DataContext inheritance and set the DataContext to self or a new DataContext.
UserControl DataContext Binding
You must be trying to use your DataTemplate as ContentTemplate for your ContentControl. As ContentTemplate operates on Content, so it will use Content as its DataContext. And your Content contains ViewModel.
Once your Button is no longer part of your DataTemplate, so it will use MainWindow's DataContext.
No seeing your comments, I am assuming that you want DataContext of UserControl to remain intact, even if your UserControl is not part of DataTemplate.
So, simple set DataContext of Button explicitly using XAML using RelativeSource.
Example,
<Button Content="{Binding Data}" DataContext="{Binding vm1, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" />
Playing with DataContext in code is not a good idea.

Draw from data of the VM

I'm learning to create WPF applications and I got a homework.
I have to create a wpf mvvm "tron lightcycle game", but unfortunately got stuck.
In the View (Mainwindow.xaml) there is a Canvas. I should draw here.
...
<Canvas Name="cnvs_game" Margin="5,5,5,5">
...
In the ViewModel there is a GameData class and a Timer.
Every tick the GameData updates(GameTime,Player1CanvasPosition (Point),... ).
I bid the Gametime to the View like this:
Mainwindow.xaml.cs:
...
<TextBlock Text="{Binding GameTime}" />
...
ViewModel.cs:
...
private GameData _GameData;
...
public String GameTime { get { return _GameData.GameTime.ToString(); } }
...
private void GameTimer_Tick(object sender, EventArgs e)
{
_GameData.Step();
OnPropertyChanged("GameTime"); // PropertyChanged with error handling
OnPropertyChanged("Player1CanvasPosition ");
OnPropertyChanged("Player2CanvasPosition ");
}
The GameTime refresh in the View. It wasn't hard. But I still have no idea how to draw.
How should I get the Player1CanvasPosition and draw there a Rectangle (in the Canvas). What is the best way to do this? Help Me Please! :S
You can do this the same way you did With the GameTime, for example:
<Canvas Name="cnvs_game" Margin="5,5,5,5">
<Rectangle Canvas.Left="{Binding Player1CanvasPositionX}" Canvas.Top="{Binding Player1CanvasPositionY}" ... />
<Rectangle Canvas.Left="{Binding Player2CanvasPositionX}" Canvas.Top="{Binding Player2CanvasPositionY}" ... />
...
And create the Player1CanvasPositionX property in the ViewModel which call the OnPropertyChanged, then when changing the properties in the ViewModel the rectangles will move.
Edit:
For dynamically adding rectangles I would use an ItemsControl which is bound to an ObservableCollection of positions. The ItemsControl datatemplate would contain a rectangle which would bind to the position. Look at this link for more details WPF Canvas, how to add children dynamically with MVVM code behind.

x:Name not working if element wrapped in UserControl's content (Silverlight)

Situation:
I have a "wrapper panel" UserControl like this (namespaces and visual details removed for brevity):
<UserControl ...>
<Grid x:Name="LayoutRoot" Background="White">
<ContentPresenter x:Name="integratedPanelContent" Margin="5" />
</Grid>
</UserControl>
Then in the Code-behind I have registered a dependency property
public FrameworkElement PanelContent
{
get { return (FrameworkElement)GetValue(PanelContentProperty); }
set { SetValue(PanelContentProperty, value); }
}
public static readonly DependencyProperty PanelContentProperty =
DependencyProperty.Register("PanelContent", typeof(FrameworkElement), typeof(MyWrapperPanel),
new PropertyMetadata(null, OnPanelContentChanged));
private static void OnPanelContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((MyWrapperPanel)d).OnSetContentChanged(e);
}
protected virtual void OnSetContentChanged(DependencyPropertyChangedEventArgs e)
{
if (PanelContent != null)
integratedPanelContent.Content = PanelContent;
}
Now I can wrap any content into my control:
<my:MyWrapperPanel x:Name="myWrap">
<my:MyWrapperPanel.PanelContent>
<TextBlock x:Name="tbxNothing" Text="Nothing" />
</my:MyWrapperPanel.PanelContent>
</my:MyWrapperPanel>
Description of the problem:
Whenever I try to use the reference tbxNothing in codebehind, the system throws NullReferenceException because tbxNothing, although as a reference exists, does not point to the TextBlock defined in XAML, but is null.
Possible (but inconvenient) workaround:
There is a workaround where I remove x:Name from the TextBlock, and then I explicitely define private TextBlock called tbxNothing. Then in the OnNavigatedTo event handler I assign the value the following way:
tbxNothing = myWrap.PanelContent as TextBlock;
This works but is not a right way to do it, because if a content is a stackpanel that contains wanted controls, I'd have to traverse the tree to find what I need, which is extremely inconvenient.
Question:
Why is the textblock no longer visible when wrapped in a User control (the way described), and how to get it by its x:Name in code-behind?
The problem is your panel content is falling between two stools. On the one hand the content with the name "tbxNothing" is create in the namescope of the main page. However its not added to the object tree at that point. On the other hand the MyWrapperPanel being a UserControl has its own namescope and its into the object tree below this that the item with then name "tbxNothing" is added. FindName on the main page won't find anything inside the MyWrapperPanel because it has its own namescope and FindName on the MyWrapperPanel won't find "tbxNothing" because it doesn't exist in its namescope (being actually created in the main page).
The answer is don't use a UserControl as a basis for MyWrapperPanel. Instead create a Silverlight Template Control. Modify the base class it inherits from to ContentControl and tweak its default template to include a ContentPresenter. Should look something like this:-
public class MyWrapperPanel : ContentControl
{
public MyWrapperPanel ()
{
this.DefaultStyleKey = typeof(MyWrapperPanel );
}
}
then in themes/generic.xaml the style can look like this:-
<Style TargetType="local:MyWrapperPanel">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MyWrapperPanel">
<Grid>
<ContentPresenter />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Your main page xaml would look like:-
<my:MyWrapperPanel x:Name="myWrap">
<TextBlock x:Name="tbxNothing" Text="Nothing" />
</my:MyWrapperPanel>
Note that deriving from ContentControl gives you a Content property which the ContentPresenter auto-magically wires to.

Setting CommandTarget to selected control in a TabControl

I have a WPF window with a few buttons and a tabcontrol having a tab for each 'document' the user is working on. The tabcontrol uses a DataTemplate to render the data in ItemSource of the tabcontrol.
The question: If one of the buttons is clicked, the command should be executed on the control rendering the document in the active tab, but I've no idea what I should set CommandTarget to. I tried {Binding ElementName=nameOfControlInDataTemplate} but that obviously doesn't work.
I tried to make my problem a bit more abstract with the following code (no ItemSource and Document objects, but the idea is still the same).
<Button Command="ApplicationCommands.Save" CommandTarget="{Binding ElementName=nestedControl}">Save</Button>
<TabControl x:Name="tabControl">
<TabControl.Items>
<TabItem Header="Header1">Item 1</TabItem>
<TabItem Header="Header2">Item 2</TabItem>
<TabItem Header="Header3">Item 3</TabItem>
</TabControl.Items>
<TabControl.ContentTemplate>
<DataTemplate>
<CommandTest:NestedControl Name="nestedControl"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
I tested the code by replacing the complete tabcontrol with only one single NestedControl, and then the command button just works.
To be complete, here is the code of NestedControl:
<UserControl x:Class="CommandTest.NestedControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Label x:Name="label" Content="Not saved"/>
</Grid>
</UserControl>
And code behind:
public partial class NestedControl : UserControl {
public NestedControl() {
CommandBindings.Add(new CommandBinding(ApplicationCommands.Save, CommandBinding_Executed));
InitializeComponent();
}
private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e) {
label.Content = "Saved";
}
}
I don't know exactly how CommandTarget works, but binding to the active tab in a TabControl is done with something like this:
"{Binding ElementName=tabControl,Path=SelectedItem}"
(SelectedItem is the current active tab)
EDIT:
More information about CommandTarget can be found here: Setting Command Target in XAML
EDIT 2:
Deleted my initial answer since it was not an answer to the question.

Change the layout of a TreeView to looks like multiple ListBox [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 8 years ago.
Improve this question
I'm trying to change the layout of a databound treeview from this:
To this:
And of course selection must works properly:
Do you have any ideas about how to do that. I've been trying to change the template but I can't find out a way to have this behavior. Maybe a component already exists...
Thanks for your help !
This is difficult. It seems to need a HierarchicalDataTemplate, but because the behavior you want requires multiple ItemsControls, it is not going to work as expected. I don't think there is a way to create a TreeView template in XAML that will do this. Your best bet is to create a custom items control of some sort. You will probably need to do the items binding in code, rather than in XAML, because without the HierarchicalDataTemplate the XAML has no way of understanding nested relationships.
That being said, if you are guaranteed to only have 2 levels of nesting (as in your example), you could do this easily with the following mark-up:
<Window.Resources>
<DataTemplate x:Key="ItemTemplate">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</Window.Resources>
<StackPanel Orientation="Horizontal">
<ListBox Name="Level1" Width="150" Height="150"
ItemsSource="{Binding Collection}"
ItemTemplate="{StaticResource ItemTemplate}"/>
<ListBox Name="Level2" Width="150" Height="150"
ItemsSource="{Binding ElementName=Level1, Path=SelectedValue.Children}"
ItemTemplate="{StaticResource ItemTemplate}"/>
<ListBox Name="Level3" Width="150" Height="150"
ItemsSource="{Binding ElementName=Level2, Path=SelectedValue.Children}"
ItemTemplate="{StaticResource ItemTemplate}"/>
</StackPanel>
Where Collection is your root items collection and there is a property on each item called Children containing the child collection.
But I think what you are asking for is an items control that can support any number of nested levels, not just 2. So in that case, I would do this in code-behind. The binding will be the same- that is, at each level, the ListBox should be bound to the parent level's items. But you will obviously need to iterate and create one ListBox for each nested level.
I finally find a way out, but like you say Charlie, it involves creating ListBox:
I create a new CustomControl which inherits Control (I couldn’t use neither Selector or TreeView because I wouldn’t have been able to manage the SelectedItem property from the derived class)
In the template of this CustomControl is an ItemsControl. This ItemsControl has its ItemTemplate property set to a DataTemplate containing a ListBox.
The CustomControl has a Depth property of type int. This property indicates the number of ListBox that should be generated.
The CustomControl automatically databound ListBoxes together: each ListBox’s ItemsSource property is databound to the SelectedItem’s children property of the previous ListBox in the visual tree.
The CustomControl has a SelectedItem property and a SelectionChanged event (like Selector-derived class).
I added an IsReallySelected attached property to the ListBoxItem which are generated. This enables to databing an IsSelected property of the ViewModel class behind the control with the IsSelected of the ListBoxItem. I had to create an attached property because its value is true when the ListBoxItem is selected AND the parent ListBox has IsSelectionActive set to true.
I blogged about this solution (with source code) on my blog.
Its too bad I didn't notice this question before you went to all that work. It is easy to restyle a TreeView to appear this way: The only code required is a single very simple attached property, "VisibleWhenCurrentOf".
The way to do it is to:
Style TreeViewItem to include a ListBox in its ControlTemplate outside the ItemsPresenter.
Control the visibility of the TreeViewItem template using "VisibleWhenCurrentOf", so that a given item is only visible inside the ItemsPresenter if it is the current item within the ListBox.
Restyling details
Here is the XAML for the relevant templates:
<ControlTemplate TargetType="TreeView">
<DockPanel>
<ListBox
ItemsSource="{TemplateBinding ItemsSource}"
IsSyncrhonizedWithCurrentItem="true"
Style="{DynamicResource BoxesTreeViewBoxStyle}"
ItemTemplate="{Binding HeaderTemplate}"
ItemTemplateSelector="{Binding HeaderTemplateSelector}" />
<ItemsPresenter />
</DockPanel>
</ControlTemplate>
<ControlTemplate TargetType="TreeViewItem">
<DockPanel
local:VisibilityHelper.VisibleWhenCurrentOf="{Binding ItemsSource, RelativeSource={RelativeSource FindAncestor,HeaderedItemsControl,2}">
<ListBox
ItemsSource="{TemplateBinding ItemsSource}"
IsSyncrhonizedWithCurrentItem="true"
Style="{DynamicResource BoxesTreeViewBoxStyle}"
ItemTemplate="{Binding HeaderTemplate}"
ItemTemplateSelector="{Binding HeaderTemplateSelector}" />
<ItemsPresenter />
</DockPanel>
</ControlTemplate>
These two templates are identical except for the conditional visibilty. The way this works is that the "+" in front of the tree item becomes a ListBox, and all items except the one selected in the ListBox are hidden.
Your BoxesTreeViewBoxStyle should set a margin around the ListBox so they will space correctly. You can actually simplify this further by putting the ListBox property values in the style, but I find it more convenient to set them in the ControlTemplate so I can restyle the ListBox without having to remember these settings.
Attached property
Here is the code for the VisibleWhenCurrentOf attached property:
public class VisibilityHelper : DependencyObject
{
// VisibleWhenCurrentOf
public static object GetVisibleWhenCurrentOf(DependencyObject obj) { return (object)obj.GetValue(VisibleWhenCurrentOfProperty); }
public static void SetVisibleWhenCurrentOf(DependencyObject obj, object value) { obj.SetValue(VisibleWhenCurrentOfProperty, value); }
public static readonly DependencyProperty VisibleWhenCurrentOfProperty = DependencyProperty.RegisterAttached("VisibleWhenCurrentOf", typeof(object), typeof(VisibilityHelper), new UIPropertyMetadata
{
PropertyChangedCallback = (sender, e) =>
{
var element = sender as FrameworkElement;
if(e.OldValue!=null)
{
var oldView = e.OldValue as ICollectionView ?? CollectionViewSource.GetDefaultView(e.OldValue);
oldView.CurrentChanged -= UpdateVisibilityBasedOnCurrentOf;
if(e.NewValue==null) element.DataContextChanged -= UpdateVisibilityBasedOnCurrentOf;
}
if(e.NewValue!=null)
{
var newView = e.NewValue as ICollectionView ?? CollectionViewSource.GetDefaultView(e.OldValue);
newView.CurrentChanged += UpdateVisibilityBasedOnCurrentOf;
if(e.OldValue==null) element.DataContextChanged += UpdateVisibilityBasedOnCurrentOf;
}
UpdateVisibilityBasedOnCurrentOf(sender);
}
});
static void UpdateVisibilityBasedOnCurrentOf(object sender, DependencyPropertyChangedEventArgs e) { UpdateVisibilityBasedOnCurrentOf(sender); }
static void UpdateVisibilityBasedOnCurrentOf(object sender, EventArgs e) { UpdateVisibilityBasedOnCurrentOf(sender); }
static void UpdateVisibilityBasedOnCurrentOf(object sender)
{
var element = sender as FrameworkElement;
var source = GetVisibleWhenCurrentOf(element);
var view = source==null ? null : source as ICollectionView ?? CollectionViewSource.GetDefaultView(source);
var visible = view==null || view.CurrentItem == element.DataContext;
element.Visibility = visible ? Visibility.Visible : Visibility.Collapsed;
}
}
There is nothing complex here: Any time DataContext or the view's Current changes, visibilty is recomputed. The PropertyChangedCallback simply sets event handlers to detect these conditions and the UpdateVisibiltyBasedOnCurrentOf handler recomputes visibility.
Advantages of this solution
Since this solution is a real TreeView:
You get all the selection handling functionality for free.
It works with any number of tree levels.
You can use all the features of HierarchicalDataTemplate, including HeaderTemplate and HeaderTemplateSelector
You can use different ItemsSource bindings at each level rather than every collection requiring a "Children" proerty
It is a lot less code than a custom control

Resources