I have a button I declare within a Stack Panel as I've written below. I want to access the button in my class so I can change the visibility such as myButton.Visibility = Visibility.Hidden but it just says myButton does not exist. It seems private to the XAML stack panel and I don't know why.
XAML
<ItemsControl x:Name="ic">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" Foreground="White" TextWrapping="Wrap" FontSize="12" Margin="0, 0, 0, 0" Width="100" VerticalAlignment="Center" Padding="0"/>
<Button x:Name="myButton" Content="X" Foreground="Red" Width="15" Height="15" Background="Transparent" VerticalAlignment="Top" BorderThickness="0" Padding="0" Click="Remove_Click"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Class
myButton.Visibility = Visibility.Hidden; //myButton doesn't exist in current context
Due to your button being declared within a DataTemplate, you cannot access it directly as with objects declared outside of it. (The DataTemplate provides the information to template your objects when added to the ItemsControl)
If you expect to only have a single , you can remove the whole object around it and gain access to your Button that way.
If you're planning on having an array of s in your , then you'll have to look into making a search logic like the one from this website:
https://dzone.com/articles/how-access-named-control
This generic extension method will search recursively for child elements of the desired type:
public static T GetChildOfType<T>(this DependencyObject depObj)
where T : DependencyObject
{
if (depObj == null)
return null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = (child as T) ?? GetChildOfType<T>(child);
if (result != null) return result;
}
return null;
}
So using that you can use like this ic.GetChildOfType<Button>();
Related
I created a textblock on a XAML form within a ContentControl. When I try to program it, C# doesn't recognize the name and I can't do anything with it.
I tried adding a textblock to the form outside of the Content Control, but that still didn't fix the problem.
Here is the XAML code:
<ContentControl>
<ContentControl.Template>
<ControlTemplate>
<Grid VerticalAlignment="Bottom" Height="250" Margin="0,450,0,0">
<Rectangle Fill="Beige" Stroke="Black" StrokeThickness="3"
Width="639" Height="250" Margin="0,0,0,0"/>
<TextBlock Text="Goal:" FontSize="18" Margin="7,50,0,0"/>
<TextBlock Text="Eaten:" FontSize="18" Margin="7,120,0,0"/>
<TextBlock Text="Remaining:" FontSize="18" Margin="7,190,0,0"/>
<TextBlock Text="Calories:" FontSize="18" Margin="140,10,0,0"/>
<TextBlock Text="Fat(g):" FontSize="18" Margin="270,10,0,0"/>
<TextBlock Text="Carbs(g):" FontSize="18" Margin="380,10,0,0"/>
<TextBlock Text="Protein(g):" FontSize="18" Margin="520,10,0,0"/>
<TextBlock x:Name="lblCalorieGoal" Text="Peb"
TextAlignment="Center" FontSize="18" Margin="-290,50,0,0"/>
</Grid>
</ControlTemplate>
</ContentControl.Template>
<TextBlock Text="TextBlock" TextWrapping="Wrap"/>
</ContentControl>
And then here is the corresponding working C# code:
public LogFood()
{
this.InitializeComponent();
Windows.Storage.ApplicationDataContainer localSettings =
Windows.Storage.ApplicationData.Current.LocalSettings;
Windows.Storage.StorageFolder localFolder =
Windows.Storage.ApplicationData.Current.LocalFolder;
Windows.Storage.ApplicationDataCompositeValue composite =
(Windows.Storage.ApplicationDataCompositeValue)localSettings
.Values["nutritionSettings"];
int calorieMin = Convert.ToInt32(composite["calorieMin"]);
int calorieMax = Convert.ToInt32(composite["calorieMax"]);
int gramsFatMin = Convert.ToInt32(composite["gramsFatMin"]);
int gramsFatMax = Convert.ToInt32(composite["gramsFatMax"]);
int gramsCarbsMin = Convert.ToInt32(composite["gramsCarbsMin"]);
int gramsCarbsMax = Convert.ToInt32(composite["gramsCarbsMax"]);
int gramsProteinMin = Convert.ToInt32(composite["gramsProteinMin"]);
int gramsProteinMax = Convert.ToInt32(composite["gramsProteinMax"]);
lblCalorieGoal.Text = calorieMin;
}
I expect to be able to change the text of the textblock. Instead, I get the error, "The name lblCalorieGoal.Text does not exist in the current context."
The key realization here is that a template is potentially a reusable part of XAML, so anything inside is in fact embedded in it a not "publicly" accessible, as there could potentially be multiple instances of the same template materialized on the view.
That being said, you can still access the materialized children inside the template indirectly by searching for them within the template using VisualTreeHelper -
internal static FrameworkElement FindChildByName(DependencyObject startNode, string name)
{
int count = VisualTreeHelper.GetChildrenCount(startNode);
for (int i = 0; i < count; i++)
{
DependencyObject current = VisualTreeHelper.GetChild(startNode, i);
if (current is FrameworkElement frameworkElement)
{
if (frameworkElement.Name == name)
return frameworkElement;
}
var result = FindChildByName(current, name);
if ( result != null)
{
return result;
}
}
return null;
}
Note, that this works only after the control has loaded (for example in the Page.Loaded event handler -
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
var block = FindChildByName(ContentRoot, "lblCalorieGoal") as TextBlock;
}
However, this all is not an ideal solution to your problem. Instead, you should either ditch the use of ContentControl altogether and have the controls in the template directly on the page (which would make them directly accessible from the code-behind), or/and use data-binding to bind data directly to appropriate controls. In this case, I would create a class to hold the data, for example:
public class NutritionInfo
{
public string CalorieGoal { get; set; }
}
Now instead of ContentControl.ControlTemplate (which replaces the template of the whole control), you will replace the ContentTemplate instead (which is just the thing which `ControlTemplate in fact displays):
<ContentControl x:Name="ContentRoot">
<ContentControl.ContentTemplate>
<DataTemplate x:DataType="local:NutritionInfo">
... your template
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
Note we use x:DataType to specify the type we bind to so that we can use x:Bind syntax. Finally, we update the template itself:
<TextBlock x:Name="lblCalorieGoal" Text="{x:Bind CalorieGoal}" ... />
We use x:Bind to bind the text of the TextBlock to the CalorieGoal property. We are almost done, now just set the Content property of the ContentControl to an instance of NutritionInfo (for example via data binding or directly):
ContentRoot.Content = new NutritionInfo()
{
CalorieGoal = "1243"
};
Overall I recommend to read further about how data-binding works in XAML, as that will help you significantly simplify your code and avoid accessing controls directly via x:Name, and decouple UI from your code. See documentation for more info.
<ListBox x:Name="my_list" Grid.Row="0" Margin="0,34,0,10">
<ItemsControl.ItemTemplate >
<DataTemplate >
<StackPanel Orientation="Horizontal" >
<CheckBox x:Name="cbx_state" Tag="{Binding}" Checked="cbx_state_Checked" Unchecked="cbx_state_Unchecked" />
<StackPanel Orientation="Vertical">
<TextBlock x:Name="txt_string" Text="{Binding}" VerticalAlignment="Center" FontSize="34" />
<TextBlock x:Name="txt_string1" Text=" Text " VerticalAlignment="Center" FontSize="20" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
I have binded the listbox with a list containing items more then 10.What i did is on application bar menu item click i wanted to check all the checkbox .But this method i have implemented have different behavior.Sometimes the child count is returned less then the actual count.Method is :
private void GetItemsRecursive(DependencyObject lb)
{
var childrenCount = VisualTreeHelper.GetChildrenCount(lb);
for (int i = 0; i < childrenCount; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(lb, i);
if (child is CheckBox)
{
CheckBox targeted_element = (CheckBox)child;
targeted_element.IsChecked = true;
if (targeted_element.IsChecked == true)
{
return;
}
}
GetItemsRecursive(child);
}
}
I am getting childrenCount as different value every time and therefore resulting in leaving some check boxes as unchecked.
The ListBox uses a virtualizing container. What that means is that the items are loaded only when they're needed (more or less just before showing on the screen).
What you can to is change the default Listbox.ItemsPanel to StackPanel
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
However, that might hurt the performance of your app, especially in the case of long lists. You're already using bindings for your checkboxes. The best solution would be to rely on MVVM - add a Checked-binding and instead of calling GetItemsRecursive execute a command on the ViewModel that sets the List-ViewModel checked property.
I have a FlipView controll which in its data template got a scrollviewer, which then got a canvas with the controls. My problem is that I need to access the canvas inside the eventhandler for the FlipView.SelectionChanged event.
The Xaml for the FlipView looks like this.
<FlipView Grid.Row="1"
d:DataContext="{d:DesignInstance model:PageContent}"
SelectionChanged="FlipView_SelectionChanged"
ItemsSource="{Binding TiffPages}"
x:Name="flBillImage">
<FlipView.ItemTemplate>
<DataTemplate>
<ScrollViewer x:Name="scrollBill"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto"
ZoomMode="Enabled"
DataContextChanged="scrollBill_DataContextChanged">
<Canvas x:Name="cvBill"
DataContextChanged="cvBill_DataContextChanged"
Loaded="cvBill_Loaded"
HorizontalAlignment="Left"
VerticalAlignment="Top"
FlowDirection="LeftToRight" >
<Image x:Name="imgBill"
Loaded="imgBill_Loaded"
DataContextChanged="imgBill_DataContextChanged"
Canvas.ZIndex="0"
Source="{Binding BillImage}"
Visibility="{Binding IsFrameExtracted, Converter={StaticResource BooleanToVisibilityConverter}}" />
</Canvas>
</ScrollViewer>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
And the C# code for parsing the visual tree looks like this:
public static List<Control> AllChildren(DependencyObject parent)
{
var _List = new List<Control>();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var _Child = VisualTreeHelper.GetChild(parent, i);
if (_Child is Control)
{
_List.Add(_Child as Control);
}
_List.AddRange(AllChildren(_Child));
}
return _List;
}
Which is used like:
var ctrls = AllChildren(flBillImage);
Checking the returned list I can find the ScrollViewer but I can't find the Canvas. I have also tried to supply the scrollviewer returned as argument to the AllChildren function but I still can't seem to find the Canvas control.
Am I doing this all wrong?
I faced a similar type of problem quite some time ago. This solution was used to access the child elements of a tree in the code-behind. Much straight-forward.
Hope this helps you.
I have a User Control,called dCB_Props that contains several objects, most importantly a ComboBox that's bound to an Observable Collection. Though the collection can take any object, it will normally take a UserControl called EditDeleteItem. I've set dCB_Props to use EditDeleteItem as an ItemsTemplate but the events aren't fired. If, on the other hand, I add an instance of EditDeleteItem then the events will get fired. I can't add items this way because the EditDeleteItem will host other controls and I'd need to use different DataTemplates.
EditDeleteItem has two Routed Events called EditClick and DeleteClick.
When the collection changes it fires an event that checks if the item added is of type EditDeleteItem. If so, then it adds handlers to the two aforementioned events.
Part of the xaml for EditDeleteClick:
<WrapPanel x:Name="wp" HorizontalAlignment="Right" Visibility="Hidden" VerticalAlignment="Center" Margin="0,0,5,0">
<Button x:Name="PART_Edit" Width="20" Height="20" Content="{DynamicResource dPen}" Style="{DynamicResource dTranspButton}" Click="btnEdit_Click"/>
<Button x:Name="PART_Delete" Width="20" Height="20" Content="{DynamicResource dCross}" Style="{DynamicResource dTranspButton}" Click="btnDelete_Click"/>
</WrapPanel>
<Label Content="{TemplateBinding Content}" Margin="2,0,45,0" Padding="0,0,0,0" HorizontalAlignment="Left" VerticalContentAlignment="Center"/>
Part of the xaml for dCB_Props:
<ComboBox HorizontalContentAlignment="Stretch" x:Name="PART_cb" Background="Transparent" Margin="0,0,0.367,0" d:LayoutOverrides="HorizontalAlignment" ItemsSource="{Binding Items, ElementName=dcb}" IsDropDownOpen="{Binding IsDropDownOpen,ElementName=dcb, Mode=TwoWay}" Grid.ColumnSpan="3" Style="{DynamicResource DaisyComboBox}" />
<Button x:Name="PART_Edit" Width="20" Height="20" Content="{DynamicResource dPen}" Visibility="Hidden" Style="{DynamicResource dTranspButton}" Margin="2.581,1.48,17.778,-1.48" Grid.Column="1" Click="btnEdit_Click"/>
<Button x:Name="PART_Delete" Width="20" Height="20" Content="{DynamicResource dCross}" Visibility="Hidden" Margin="22.602,1.48,-2.243,-1.48" Style="{DynamicResource dTranspButton}" Grid.Column="1" Click="btnDelete_Click"/>
<Button x:Name="PART_Add" Content="+" Grid.Column="3" Margin="0,0,0,0" Style="{DynamicResource dTranspButton}" Click="btnAdd_Click"/>
Note the above two are codes just for objects, I've left out Column Definitions, Event Triggers, etc.
Part of dCB_Props.xaml.cs code is:
public partial class dCB_Props : UserControl
{
public dCB_Props()
{
this.InitializeComponent();
Items= new ObservableCollection<object>();
Items.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Items_CollectionChanged);
}
void Items_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
{
foreach (var o in e.NewItems)
{
if (o.GetType() == typeof(EditDeleteItem))
{
EditDeleteItem itm = (EditDeleteItem)o;
itm.EditClick += new RoutedEventHandler(ItemEdit_Click);
itm.DeleteClick += new RoutedEventHandler(ItemDelete_Click);
}
}
}
}
...//I've left some code here since I don't deem it's that important for the situation
private void ItemEdit_Click(object sender, RoutedEventArgs e)
{
DependencyObject d = GetTemplateChild("PART_cb");
if (d == null) return;
ComboBox cb = (ComboBox)d;
if (cb.SelectedItem != null) RaiseEvent(new RoutedEventArgs(EditClickEvent, e.OriginalSource));
}
}
The above works if I add an item of type EditDeleteItem and remove the ItemTemplate property for the Label that resides inside dCB_Props. It also works if I set the ItemTemplate, shown below, in EditDeleteItem's ContentTemplate. But, as mentioned, I need to use different Data Templates so I assume all Data Templates will have to reside in a Resource Dictionary and then I'd have to use a Template Selector.
Data Template:
<DataTemplate x:Shared="false" x:Key="TagTemplate">
<local:EditDeleteItem x:Name="edItem">
<local:EditDeleteItem.Content>
<StackPanel>
<TextBlock Text="{Binding Path=Content.Label}"/>
<CheckBox Content="Isolated" IsChecked="{Binding Content.IsIsolated}"/>
<CheckBox Content="Match Case" IsChecked="{Binding Content.MatchCase}"/>
<CheckBox Content="Include" IsChecked="{Binding Content.Include}"/>
</StackPanel>
</local:EditDeleteItem.Content>
</local:EditDeleteItem>
</DataTemplate>
I believe I need to use command bindings. But not really sure where to put the CommandBindings, and not so sure how to use them, though I've read a page or two.
Thanks,
Hassan
The events are fired, but you don't catch them, because subscription in Items_CollectionChanged never occurs if ItemTemplate is used.
You should understand how ItemsControl (and ComboBox) works with ItemsSource. ItemsControl use ItemContainerGenerator to populate its visual tree. Each item from ItemsSource wrap into container which derived from ContentControl. Then item is set as a Content, ItemTemplate is set as ContentTemplate and so on. When you put EditDeleteItem into ItemTemplate it becomes a part of visual tree but not an item. That's why there is no EditDeleteItem in e.NewItems and no subscription.
The right way is Commands, as you mentioned. You should declare two commands:
public class EditDeleteItem : UserControl
{
...
public static readonly RoutedUICommand EditCommand = new RoutedUICommand(...);
public static readonly RoutedUICommand DeleteCommand = new RoutedUICommand(...);
...
}
Now the part of template may look like:
<WrapPanel ...>
<Button ... Command="{x:Static EditDeleteItem.EditCommand}"/>
<Button ... Command="{x:Static EditDeleteItem.DeleteCommand}"/>
</WrapPanel>
Then you add command bindings to dCB_Props:
public partial class dCB_Props : UserControl
{
static dCB_Props()
{
...
CommandManager.RegisterClassCommandBinding(
typeof(dCB_Props),
new CommandBinding(EditDeleteItem.EditCommand, OnEditCommandExecuted));
CommandManager.RegisterClassCommandBinding(
typeof(dCB_Props),
new CommandBinding(EditDeleteItem.DeleteCommand, OnDeleteCommandExecuted));
...
}
...
}
You need to implement OnEditCommandExecuted and OnDeleteCommandExecuted in order to handle corresponding commands from EditDeleteItem.
I hope I understood your question correctly ;)
I have a Datatemplete for List-box Item in which I have a Grid with two columns using WPF. In the first column I want to put few customized controls(Buttons) dynamically using C# in code behind. I don't know how to start and from where should I start, can anybody please help me with some great inputs and examples. Any answer will be greatly appreciate.
Thanks in advance.
XAML code:
<ListBox x:Name="ListBoxItem"
Grid.Row="1"
SelectionMode="Extended"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"
FocusVisualStyle="{x:Null}"
KeyboardNavigation.IsTabStop="False"
Background="DarkGray"
ItemsSource="{Binding}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel x:Name="ListContent"
IsItemsHost="True"
Width="500"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel LastChildFill="True"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<StackPanel DockPanel.Dock="Left"
Width="30"
Height="{Binding Height}">
<--Here I want to put few customize buttons in code behind-->
</StackPanel>
<Image x:Name="MainPage"
Stretch="UniformToFill"
Source="{Binding ImagePath}"
Height="{Binding Height}"
Width="{Binding Width}"/>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
You specified wanting to use code behind, so it would look something like this:
XAML:
<StackPanel Initialized="StackPanel_Initialized" .. />
Code behind:
using MyNamespace;
private void StackPanel_Initialized(object sender, EventArgs e)
{
MyControl newItem = new MyControl();
// Set any other properties
StackPanel parent = sender as StackPanel;
parent.Children.Add(newItem);
}
If you are looking for adding Controls inside a the First column of your grid then put a Panel inside the first column and in code behind add controls as child to that Panel. So as you mentioned in above that you are using DataTemplete then I would like to say that you can access that Panel something like:
Put the below codes inside the event where you wnt to add the controls.
ListBoxItem item = (ListBoxItem)(this.lst.ItemContainerGenerator.ContainerFromIndex(i));
ContentPresenter presenter = FindVisualChild<ContentPresenter>(item);
DataTemplate template = presenter.ContentTemplate;
StackPanel stack = (StackPanel)template.FindName("FirstColumn Panel Name", presenter);
and then call the below method:
private childItem FindVisualChild<childItem>(DependencyObject obj)
where childItem : DependencyObject
{
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;
}