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.
Related
First time really using WPF - thought I'd have a go at remaking something I did a while back in Java.
I'm trying to bind the Text value of a TextBlock on a popup to something that gets set in the backend, so I can use one handler method to display any message on said popup.
I've been trying multiple different routes, such as fully binding it in the cs instead of XAML like so:
<--XAML-->
<Popup Margin="89,75,0,0" Name="verif_popup" HorizontalAlignment="Left" VerticalAlignment="Top" IsOpen="False" PopupAnimation="Slide" Placement="Center" Width="100" Height="100" Grid.Column="1">
<Popup.Effect>
<BlurEffect/>
</Popup.Effect>
<Canvas Background="Azure">
<TextBlock Name="VerifTextBlock"/>
</Canvas>
</Popup>
<--CS-->
private void SmallPopupHandler(string text)
{
Binding binding = new("Text")
{
Source = text
};
VerifTextBlock.SetBinding(TextBlock.TextProperty, binding);
verif_popup.IsOpen = true;
}
But it doesn't like the fact that the string isn't a TextBlock property, I sort of knew this wouldn't work but it seems the most logical to me having come from swing. There also doesn't seem to be a way for me to cast it to it and im not in the mood for making my own dependency property rn...
The next thing I tried was to bind the value to a field in the class, but I just got a stackoverflow error (haha nice)
<--XAML-->
<Popup Margin="89,75,0,0" Name="verif_popup" HorizontalAlignment="Left" VerticalAlignment="Top" IsOpen="False" PopupAnimation="Slide" Placement="Center" Width="100" Height="100" Grid.Column="1">
<Popup.Effect>
<BlurEffect/>
</Popup.Effect>
<Canvas Background="Azure">
<Canvas.DataContext>
<local:MainWindow/>
</Canvas.DataContext>
<TextBlock Name="VerifTextBlock" Text="{Binding Popup_message}"/>
</Canvas>
</Popup>
<--CS-->
public partial class MainWindow : Window
{
public string? Popup_message { get; set; }
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
I also tried making an interfacing class of sorts to see if that would work around the stackoverflow error (haha) but as im sure you could have guessed by now, that didn't work either...
Kinda pulling my hair out so any help would be greatly appreciated!
Thanks in advance!
You could just set the Text property of the VerifTextBlock directly as suggested by #Clemens:
private void SmallPopupHandler(string text)
{
VerifTextBlock.Text = text;
verif_popup.IsOpen = true;
}
If you really do want to use a binding for whatever reason, then remove the binding path. This should work:
private void SmallPopupHandler(string text)
{
Binding binding = new()
{
Source = text
};
VerifTextBlock.SetBinding(TextBlock.TextProperty, binding);
verif_popup.IsOpen = true;
}
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>();
i want to change textblock text in page initialize event
here is my xaml
<ListBox Margin="3,60,1,10" BorderThickness="2" Grid.Row="1" Name="lstAnnouncement" Tap="lstAnnouncement_Tap" Width="476" d:LayoutOverrides="VerticalMargin">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Name="thispanel" Grid.Row="1" Orientation="Horizontal" Height="120" Width="478" >
<StackPanel.Background>
<ImageBrush ImageSource="Images/Text-ALU.png" Stretch="Fill" />
</StackPanel.Background>
<Grid HorizontalAlignment="Left" Width="30" Margin="0,0,0,2" Background="#FF0195D5" Height="118">
<TextBlock x:Name="txtDate" TextWrapping="Wrap">
</TextBlock>
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
i want to change txtDate.Text using c# in code-behind but txtdate is not accessible in code behind so how to achieve it ?
The reason you're not able to access the txtDate object is because it's contained within the DataTemplate you're using for the ListBox. This isn't an error - the DataTemplate is being applied to every single item added to your ListBox.
Given that the ListBox creates, among other controls, a Grid containing a TextBlock with the name "txtDate", for every single item added to it, what would it mean to access the txtDate object? How would your program decide which of a (functionally) infinite number of txtDates associated with an identical number of ListBoxItems you meant when you referenced txtDate?
If you wanted to be able to easily change the content of txtDate, you'd want to bind the ItemsSource of your ListBox to a property in a ViewModel. The easiest way to do this would be to have that property be an IEnumerable containing a custom model type. This way, you could update the text property of that model and call NotifyPropertyChanged on the that property, and the UI would update to reflect the new data.
Here's an example:
public class YourViewModel
{
public List<YourModel> Models { get; set; }
}
public class YourModel : INotifyPropertyChanged
{
private string yourText;
public string YourText
{
get { return yourText; }
set
{
yourText = value;
NotifyPropertyChanged("YourText");
}
}
// add INotifyPropertyChanged implementation here
}
And then you'd want to bind the ItemsSource of the ListBox to YourViewModel's Models property, and the text of your TextBox to the YourModel's YourText property. Any time you change the YourModel.YourText property, it'll automatically update on the UI. I think it's probably subject to debate whether having your model implement INotifyPropertyChanged is proper MVVM, but I find it a lot easier in these cases than forcing the ViewModel to update every single model each time a change is made on one of them.
If you're not familiar with the MVVM pattern used with WPF, this might be a good start: MVVM example.
this function will help you... This will help u find the control inside of a listbox runtime..
public FrameworkElement SearchVisualTree(DependencyObject targetElement, string elementName)
{
FrameworkElement res = null;
var count = VisualTreeHelper.GetChildrenCount(targetElement);
if (count == 0)
return res;
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(targetElement, i);
if ((child as FrameworkElement).Name == elementName)
{
res = child as FrameworkElement;
return res;
}
else
{
res = SearchVisualTree(child, elementName);
if (res != null)
return res;
}
}
return res;
}
Here first parameter is parent and the second parameter is the name of the element which in your case is "txtDate".. hope it works!!
What I am attempting to do is have a collection of items shown in a GridView control and have the size of these items change based on a command executed by a separate button.
For example, having a row of buttons across the top reading “Small”, “Medium” and “Large” and having the items in the GridView respond to the relevant command by displaying its items in the relevant state.
I have the gridview declared like so
<GridView ItemsSource="{Binding Squares}"
With Squares being an observable collection of Square objects that have a Title and a Fill property.
At first I went down the DataTemplateSelector route by declaring the following data templates in the Resources section of the page.
<DataTemplate x:Key="SquareSmallTemplate">
<Grid Height="100" Width="100">
<Rectangle Fill="{Binding Fill}"/>
<TextBlock Text="{Binding Title}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="SquareMediumTemplate">
<Grid Height="150" Width="150">
<Rectangle Fill="{Binding Fill}"/>
<TextBlock Text="{Binding Title}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="SquareLargeTemplate">
<Grid Height="200" Width="200">
<Rectangle Fill="{Binding Fill}"/>
<TextBlock Text="{Binding Title}"/>
</Grid>
</DataTemplate>
The idea being that the grid’s height and width properties are different for the relevant template. I declared the following data templates in the selector
public DataTemplate SmallTemplate { get; set; }
public DataTemplate MediumTemplate { get; set; }
public DataTemplate LargeTemplate { get; set; }
And in the SelecteTemplateCore method I just returned the relevant template
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
string value = item as string;
if (value != null)
{
if (value == "Small")
return SmallTemplate;
else if (value == "Medium")
return MediumTemplate;
else if (value == "Large")
return LargeTemplate;
return base.SelectTemplate(item, container);
}
else
{
return base.SelectTemplateCore(item, container);
}
}
However, with this method (and, by design of the DataTemplateSelector) the object being passed in is the item in the collection (the Square).
This is fine if I wanted each item to have a different appearance or something, but what I need is the template to change based on another property on the view model.
For this, I have the following
public string State {get; set;}
and this is set to “Small”, “Medium, or “Large based on a separate row of three buttons that execute a command that sets this property to the relevant value.
How do I relate the State property to changing to the relevant DataTemplate?
Another route I tried was to have a single Data template that used the VSM to animate the Height/Width properties in the relevant states. However I could not get the relevant animation to execute when the State changed.
Any help would be great, thanks
There are a few ways to do this, I'm not sure which would be best. In any case, you'll need 1) a trigger, and 2) the action to update the template. I am leaning towards using PropertyChangedTrigger along with an InvokeCommandAction.
<GridView x:Name="grid">
<i:Interaction.Triggers>
<ei:PropertyChangedTrigger Binding="{Binding State}">
<i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=UpdateTemplateCommand}" CommandParameter="{Binding State}" />
</ei:PropertyChangedTrigger>
</i:Interaction.Triggers>
<GridView>
(Here the "AncestorType" would just be the root of the view, so please change "UserControl" as needed.)
Then in the view, you would have an ICommand that updates the template:
UpdateTemplateCommand = new DelegateCommand(state => {
switch ((string)state)
{
default:
case "Small" : grid.ItemTemplate = "SquareSmallTemplate"; break;
case "Medium" : grid.ItemTemplate = "SquareMediumTemplate"; break;
case "Large" : grid.ItemTemplate = "SquareLargeTemplate"; break;
}
});
IDK ... after writing this out it seems a bit convoluted. Maybe you'd find it preferable to add a CurrentDataTemplate property to the view-model, and assign it by creating DataTemplates from strings using XamlReader.
How can I access page resource element in C# coding? I have the following piece of code in my XAML. I want to access the image element in my C# Code, but it is not accessible.
<Page.Resources>
<DataTemplate x:Key="Standard250x250ItemTemplate">
<Grid HorizontalAlignment="Left" Width="150" Height="150">
<Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}">
<Image x:Name="image" Source="{Binding Image}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Title}" />
</Border>
</Grid>
</DataTemplate>
It is not accessible because a DataTemplate resource does not get instantiated until it is loaded. You would need to do something like this to load it first:
var dataTemplate = (DataTemplate)this.Resources["Standard250x250ItemTemplate"];
var grid = dataTemplate.LoadContent();
and then traverse the element tree to get to the Image.
A better approach in many scenarios is to define an attached dependency property or attached behavior that you can attach to your Image in XAML and write code related to the associated Image.
It depends on when you are trying to access it. If trying to access the image control of elements that have already been rendered then you can use ItemContainerGenerator like such:
//assumes using a ListView
var item = (ListViewItem)listView.ItemContainerGenerator.ContainerFromItem(myModel);
// traverse children
var image = GetChildOfType<Image>(item);
// use the image!
private T GetChildOfType<T>(DependencyObject obj)
{
for(int i = 0; i< VisualTreeHelper.GetChildrenCount(obj); i++)
{
var child = VisualTreeHelper.GetChild(obj, i);
if(child is T) return child as T;
T item = GetChildOfType<T>(child);
if(item != null) return item;
}
return null;
}
If you need to change properties of the image, then that can be accomplished through binding as well.