ContentControl get all Elements inside DataTemplate without knowing the Name? - wpf

i want to get a list with all elements of my current usercontrol. Not only the one in the LogicalTree, ALL elements inside the defined and used datatemplates in the usercontrol.
When iam iterating threw the VisualTree it dont have VisualTree items inside the ContentControl. I thought the VisualTree contains all Elements?
So at the end i need the TextBox and the Button inside the DataTemplate in my List. But i dont know the x:Name of the elements.
Can somebody help?
<UserControl
x:Class="DataTemplate.Test"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid x:Name="LayoutRoot">
<ContentControl>
<ContentControl.ContentTemplate>
<DataTemplate>
<Grid VerticalAlignment="Stretch">
<Button x:Name="btn_1" />
<TextBlock x:Name="tb_1" />
</Grid>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</Grid>
</UserControl>
In code i iterate threw it when UserControl.Loaded event is fired...
public void OnUserControlLoaded(object sender, EventArgs e)
{
BindChildren(LayoutRoot);
}
List<object> list = new List<object>();
private void BindChildren(object target) {
try
{
var count = VisualTreeHelper.GetChildrenCount(target as FrameworkElement);
if(count < 1)
{
foreach (var child in LogicalTreeHelper.GetChildren(_currentElement))
{
list.Add(child);
BindChildren(child);
}
}
else
{
for (int i = 0; i < count; i++)
{
list.Add(VisualTreeHelper.GetChild(target as FrameworkElement, i));
BindChildren(VisualTreeHelper.GetChild(target as FrameworkElement, i));
}
}
}
catch (InvalidCastException exc)
{
throw;
}
}

Related

WPF: Placing game pieces on board

I'm currently creating a board game in WPF. I'm creating my board in my PlayerViewModel so I have a nice 10x10 board. I also create pieces that the player starts with (4). Here is where I get stuck, because I'm not quite sure if it's possible to just put the player's pieces in my GamePiece.xaml the way I've done it since this is the "component" that creates the whole board for me. The circle/image is the component I want as a player's disc.
I bind this into my GameView.xaml, however the problem is that I want to have circles as my pieces in the game. Obviously here I'm just creating the whole board and so it's also creating the pieces (circles/photos) and I can't seem to manipulate this and decide how many I want to show on the board in the beginning. I have tried different ways, like put the pieces on specific coordinates and just having the color of the square it's taking up on the board change, but it doesn't look very nice.
First, you would create a ListView and configure it to place the items in rows (by using WrapPanel as ItemsPanel and setting the width and height for the ListView), and -as you mentioned- you can use BoardPiece UserControl as ItemTemplate.
Your GameView.xaml can be something similar to this
<UserControl
x:Class="GameView">
<StackPanel Orientation="Vertical">
<ListView
Width="600"
Height="500"
ItemsSource="{Binding BoardPieces}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<testingThings:BoardPiece Margin="0" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button
Width="100"
Margin="8"
Click="ButtonBase_OnClick"
Content="Fill All" />
</StackPanel>
</UserControl>
Then, Configure the ViewModel: create your items and store them in an ObservableCollection to bind them later with the ListView..
public class GameViewViewModel
{
public ObservableCollection<BoardPieceItem> BoardPieces { set; get; } =
new ObservableCollection<BoardPieceItem>();
public GameViewViewModel()
{
for (var i = 0; i < 10; i++)
for (var j = 0; j < 10; j++)
{
// random coloring at initialization, do it as you want..
BoardPieces.Add(new BoardPieceItem
{
Index = (i, j),
RectangleColor = "#00FF00", // green
EllipseColor = (i + j) % 2 == 0
? "#00FFFFFF" // transparent
: "#000000", // Black
});
}
}
}
Now, Bind the ViewModel with the View
public partial class GameView
{
public GameView()
{
InitializeComponent();
DataContext = new GameViewViewModel();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
if (sender is Button { DataContext: TestDialogVm vm })
{
foreach (var item in vm.BoardPieces)
{
item.EllipseColor = "#0000FF";
}
}
}
}
In BoardPiece.xaml there is no need to use Converters
<UserControl
x:Class="SharedModule.Views.TestLab.BoardPiece"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:testLab="clr-namespace:SharedModule.Views.TestLab"
d:DataContext="{d:DesignInstance testLab:BoardPieceItem}"
MouseDown="UIElement_OnMouseDown"
mc:Ignorable="d">
<Grid>
<Rectangle
Width="45"
Height="45"
Fill="{Binding RectangleColor}"
Stroke="Black"
StrokeThickness="1.3" />
<Ellipse
Width="20"
Height="20"
Fill="{Binding EllipseColor}" />
</Grid>
</UserControl>
Finally, Configure the BoardPieceItem to make it possible to update the game board at runtime..
public class BoardPieceItem : INotifyPropertyChanged
{
public (int, int) Index { get; set; }
private string _rectangleColor;
public string RectangleColor
{
get => _rectangleColor;
set
{
_rectangleColor = value;
OnPropertyChanged();
}
}
private string _ellipseColor;
public string EllipseColor
{
get => _ellipseColor;
set
{
_ellipseColor = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Now you are all set, see how UIElement_OnMouseDown will update the item's color, and ButtonBase_OnClick will update the cells of the board at all (I could go all the way MVVM, but used some events in a hurry).
This is how the game looks like at my side

why the name of the Controls in ItemsControl not displaying in Codebehind file in Wpf

i Created simple sample using the ItemsControl and DataTemplate.. i want to bind the values using the C# code in textblocs.But i didn't get the textblock names and Datatemplate names in Code behind file please tell me why.. What to do to get the names of the controls ?
<ItemsControl ItemsSource="{Binding Path=.}" >
<ItemsControl.ItemTemplate>
<DataTemplate x:Name="datatemp">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="Textblock1" Text="{Binding }" FontWeight="Bold" ></TextBlock>
<TextBlock Text=", " />
<TextBlock Text="{Binding }" x:Name="Textblock2"></TextBlock>
<TextBlock Text=", " />
<TextBlock Text="{Binding }" x:Name="Textblock3"></TextBlock>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
but here in Code file Textblock1 and other names not dispalying even i used the "Name" only Instead of "x:Name "
No member will be generated for the DataTemplate. A datatemplate is just used for instantiating items dynamically at runtime, so the Controls don't even exist until you add items to your ItemsControl, and even then i think the names of individual controls inside the DataTemplate are just useful for usage from inside the DataTemplate's markup.
You can generate names, if needed when the ItemsControl is loaded.
private void OnPopupItemsLoaded(object sender, RoutedEventArgs e)
{
var itemsControl = sender as ItemsControl;
itemsControl.ApplyTemplate();
var numItems = itemsControl.ItemContainerGenerator.Items.Count();
for (var i = 0; i < numItems; i++)
{
var container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i);
textBlock = FindVisualChild<TextBlock>(container);
if (textBlock != null)
{
textBlock.Name = SanitizeName(textBlock.Text);
textBlock.Uid = $"Item{i}";
}
}
}
private static T FindVisualChild<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
return (T)child;
}
T childItem = FindVisualChild<T>(child);
if (childItem != null)
{
return childItem;
}
}
}
// None found
return null;
}
// FrameworkeElement.Name must start with an underscore or letter and
// only contain letters, digits, or underscores.
private string SanitizeName(string textString)
{
// Text may start with a digit
var sanitizedName = "_";
foreach (var c in textString)
{
if (char.IsLetterOrDigit(c))
{
sanitizedName += c;
}
else
{
sanitizedName += "_";
}
}
return sanitizedName;
}

How to add selected item from AutoCompleteBox to ListBox or Datagrid?

I am trying to build the scenario where I can pick any item from a collection of items and being able to add this text string to a listbox or datagrid by clicking ‘Add’ button. I also need to be able to remove the item from listbox or datagrid by clicking 'Remove' button. I started this but have problem to make it work. I am wondering what the problem is. Any ideas are highly appreciated. Thank you!
XAML:
<UserControl
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
x:Class="AutoCompleteBoxSimpleTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit"
mc:Ignorable="d" >
<StackPanel x:Name="LayoutRoot" Background="White" Width="150">
<TextBlock Text="{Binding ElementName=MyAutoCompleteBox, Path=SelectedItem, TargetNullValue='No item selected', StringFormat='Selected Item: {0}'}" />
<sdk:AutoCompleteBox x:Name="MyAutoCompleteBox" IsTextCompletionEnabled="True" ItemsSource="{Binding Items}" />
<Button x:Name="AddButton" Click="AddButton_Click" Content="AddButton" />
<Button x:Name="RemoveButton" Click="RemoveButton_Click" Content="RemoveButton" />
<ListBox x:Name="ListBox" BorderThickness="0" SelectionMode="Multiple" />
<sdk:DataGrid x:Name="dgEditPackageProperties_ADEntities">
<sdk:DataGrid.Columns>
<sdk:DataGridTemplateColumn x:Name="dgtcEditPackageProperties_Icon"/>
<sdk:DataGridTextColumn x:Name="dgtcEditPackageProperties_Entities" Header="AD Entities with Access" />
</sdk:DataGrid.Columns>
</sdk:DataGrid>
</StackPanel>
Code Behind:
public partial class MainPage : UserControl
{
private IList<string> myDataList = null;
string currentItemText;
public IList<string> Items
{
get;
private set;
}
public MainPage()
{
InitializeComponent();
Items = new List<string>();
Items.Add("One");
Items.Add("Two");
Items.Add("Three");
Items.Add("Four");
DataContext = this;
}
private void AddButton_Click(object sender, RoutedEventArgs e)
{
if (MyAutoCompleteBox.SelectedItem != null)
{
foreach (var item in MyAutoCompleteBox.SelectedItem)
{
ListBox.Items.Add(item);
myDataList.Remove(item);
}
ApplyDataBinding();
}
}
private void RemoveButton_Click(object sender, RoutedEventArgs e)
{
if (ListBox.SelectedItems != null)
{
int count = ListBox.SelectedItems.Count - 1;
for (int i = count; i >= 0; i--)
{
//myDataList.Add(ListBox.SelectedItems[i]);
ListBox.Items.Remove(ListBox.SelectedItems[i]);
}
ApplyDataBinding();
}
}
private void ApplyDataBinding()
{
MyAutoCompleteBox.ItemsSource = null;
MyAutoCompleteBox.ItemsSource = myDataList;
}
}
For starters, if you use an ObservableCollection<> for myDataList instead of List<> you can just add and remove items and the control will auto-update.
Second, try not to do remove items while iterating over them. Put them in a seperate List first.
And finally, where are you even CREATING myDataList ? :)
Instead of giving the autocomplete box a name and checking its selected item that way, you can just bind to its selectedItem property. Then in your "AddButton_Click" you can simply add the selectedItem you bind to.

Silverlight: retrieving controls nested within a Listbox

i made a listbox that generates dynamic controls such as dropdowns & datepicker. i wanted to retrieve the data within the rows. Normally, in windows forms we commonly index the ...Items[i].FindControl("ControlID") method. How do you do about in XAML?
I need to retrieve the changes upon clicking a button.
btw, here's a simple view of my xaml:
<ListBox>
<stackpanel>
<TextBlock />
<stackpanel>
<grid>
<combobox />
<combobox/>
<datepicker />
</grid>
</stackpanel>
</stackpanel>
</ListBox>
Thank you so much!
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
FrameworkElement selectedItem = (sender as ListBox).SelectedItem as FrameworkElement;
List<FrameworkElement> children = new List<FrameworkElement>();
children = GetChildren(selectedItem, ref children);
}
private List<FrameworkElement> GetChildren(FrameworkElement element, ref List<FrameworkElement> list)
{
int count = VisualTreeHelper.GetChildrenCount(element);
for (int i = 0; i < count; i++)
{
FrameworkElement child = VisualTreeHelper.GetChild(element, i) as FrameworkElement;
if(child != null)
{
list.Add(child);
GetChildren(child, ref list);
}
}
return list;
}
This returns all the FrameworkElements (including paths, borders etc). You can easily extend it and call the GetChildren method recursively only if the child is of certain type (ComboBox, StackPanel etc)
I have a helper class with the following two methods to assist with this sort of task.
XAML:
<ListBox Height="236" HorizontalAlignment="Left" Margin="31,23,0,0"
Name="listBox1" VerticalAlignment="Top" Width="245">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Name="sp">
<TextBlock Name="id">id</TextBlock>
<TextBox Name="test" Text="{Binding Key}"></TextBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Using with a List box, you could pass in the selected item:
var v1 =(ListBoxItem) listBox1.ItemContainerGenerator.ContainerFromIndex(
listBox1.SelectedIndex);
TextBox tb = GetChildByName<TextBox>(v1, "test");
tb.Text = "changed";
and you would get the correct textbox for that selected list box item. You can then use that reference to change properties on it.
public T GetChildByName<T>(DependencyObject parent, string name)
where T : class
{
T obj = RecGetChildByName<T>(parent, name) as T;
if (obj == null) throw new Exception("could find control "
+ "of name as child");
return obj;
}
private DependencyObject RecGetChildByName<T>(DependencyObject parent,
string name)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
Control childControl = child as Control;
if (childControl != null)
{
if (childControl.Name == name) return child;
}
if (VisualTreeHelper.GetChildrenCount(child) > 0)
return RecGetChildByName<T>(child, name);
}
return null;
}
The most straightforward way would be to set a two-way binding on of your controls to objects and then the objects will tell you what the values were set to.
Also you can go through your tree by going through the Content properties of the objects until you get to the leaf objects.
Alternatively, you can use the Selected item and call the VisualTreeHelper's GetChild Method until you're at the leaf objects.

Outside Property inside a DataTemplate WPF

Scenario: I have a ListBox and the ListBoxItems have a DataTemplate. What I want to do is put a ContextMenu in the DataTemplate. The catch is that I want this ContextMenu ItemsSource to be different depending on certain properties in the window. My initial thought is that I could just bind the ItemsSource to a Property in the window and that would return an ItemsSource; however, I cant seem to bind to this property correctly. I believe this is because I am in the DataTemplate and consequently the DataContext (I believe that is the right word) is of that ListBoxItem and not of the window.
How could I get the ContextMenu that is inside a DataTemplate to bind to a Property outside of the DataTemplate.
You can get the DataContext from your window by using the RelativeSource FindAncestor syntax
<DataTemplate>
<TextBlock Text="{Binding MyInfo}">
<TextBlock.ContextMenu>
<Menu ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.MyContextMenuItems}"/>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
Not totally sure, but the binding is correct...
If your DataContext is on another object type, you just have to change the AncestorType (eg. by UserControl).
This might be a good candidate for an AttachedProperty. Basically what you would do is wrap your ContextMenu in a UserControl and then add a Dependency Property to the UserControl. For example:
MyContextMenu.xaml
<UserControl x:Class="MyContextMenu" ...>
<UserControl.Template>
<ContextMenu ItemSource="{Binding}" />
</UserControl.Template>
</UserControl>
MyContextMenu.xaml.cs
public static readonly DependencyProperty MenuItemsSourceProperty = DependencyProperty.RegisterAttached(
"MenuItemsSource",
typeof(Object),
typeof(MyContextMenu),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender)
);
public static void SetMenuItemsSource(UIElement element, Boolean value)
{
element.SetValue(MenuItemsSourceProperty, value);
// assuming you want to change the context menu when the mouse is over an element.
// use can use other events. ie right mouse button down if its a right click menu.
// you may see a perf hit as your changing the datacontext on every mousenter.
element.MouseEnter += (s, e) => {
// find your ContextMenu and set the DataContext to value
var window = element.GetRoot();
var menu = window.GetVisuals().OfType<MyContextMenu>().FirstOrDefault();
if (menu != null)
menu.DataContext = value;
}
}
public static Object GetMenuItemsSource(UIElement element)
{
return element.GetValue(MenuItemsSourceProperty);
}
Window1.xaml
<Window ...>
<Window.Resources>
<DataTemplate TargetType="ListViewItem">
<Border MyContextMenu.MenuItemsSource="{Binding Orders}">
<!-- Others -->
<Border>
</DataTemplate>
</Window.Resources>
<local:MyContextMenu />
<Button MyContextMenu.MenuItemsSource="{StaticResource buttonItems}" />
<ListView ... />
</Window>
VisualTreeHelpers
public static IEnumerable<DependencyObject> GetVisuals(this DependencyObject root)
{
int count = VisualTreeHelper.GetChildrenCount(root);
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(root, i);
yield return child;
foreach (var descendants in child.GetVisuals())
{
yield return descendants;
}
}
}
public static DependencyObject GetRoot(this DependencyObject child)
{
var parent = VisualTreeHelper.GetParent(child)
if (parent == null)
return child;
return parent.GetRoot();
}
This example is un-tested I'll take a look later tonight and make sure its accurate.

Resources