Unable to access UserControl using X:Name from XAML Page - c#

This is my user control embedded into my page list view
<ListView.ItemTemplate>
<DataTemplate x:DataType="data:ZTask">
<local:AddUserControl x:Name="MyUserControl"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" />
</DataTemplate>
</ListView.ItemTemplate>
I have Named it as MyUserControl using X: Name. I tried accessing this in my page cs file but it gives me an error saying "The name 'MyUserControl' does not exist in the current context".
Help me fix this issue.

Which particular instance of AddUserControl are you expecting to get a reference to since there will be an AddUserControl added per item in the ListView?
If you want to do something with the AddUserControls, you could handle the Loaded event for each one of them:
<ListView.ItemTemplate>
<DataTemplate x:DataType="data:ZTask">
<local:AddUserControl x:Name="MyUserControl"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Loaded="OnLoaded"/>
</DataTemplate>
</ListView.ItemTemplate>
private void OnLoaded(object sender, RoutedEventArgs e)
{
AddUserControl auc = (AddUserControl)sender;
//...
}

Related

Wrong type in ListDetailsView DetailsTemplate

I'm working on a UWP app using the Windows Community Toolkit. In some pages, I use the ListDetailsView (FKA MasterDetailsView) like this :
<!-- page and stuff... -->
<controls:ListDetailsView
x:Name="ListViewParameters"
ItemsSource="{x:Bind ViewModel.ParametersList, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.Selected, Mode=TwoWay}"
CompactModeThresholdWidth="720"
BorderBrush="Transparent"
NoSelectionContent="Please select a parameter to view"
ListPaneBackground="{ThemeResource SystemControlPageBackgroundChromeLowBrush}"
ListPaneWidth="350"
SelectionChanged="ListViewParameters_SelectionChanged">
<controls:ListDetailsView.ItemTemplate>
<DataTemplate x:DataType="parameters:ParameterSet">
<!-- item template, works great -->
</DataTemplate>
</controls:ListDetailsView.ItemTemplate>
<controls:ListDetailsView.DetailsTemplate>
<DataTemplate>
<ScrollViewer x:Name="ContentScrollViewer">
<StackPanel x:Name="Details" Margin="{StaticResource MediumLeftMargin}">
<!-- this is where problems happen -->
<TextBlock Style="{StaticResource TitleTextBlockStyle}"
Text="{Binding Name, Mode=OneWay}"
x:Name="title"
Margin="{StaticResource MediumBottomMargin}"/>
<!-- other elements with bindings... -->
</StackPanel>
</ScrollViewer>
</DataTemplate>
</controls:ListDetailsView.DetailsTemplate>
</controls:ListDetailsView>
My problem appears when the page loads: the first thing bound to the DetailsTemplate appears to be the page's DataContext, which is a ViewModel. I can see this in the output console :
Error: BindingExpression path error: 'Name' property not found on 'MyProject.ParametersViewModel'. BindingExpression: Path='ParameterSet.Name' DataItem='MyProject.Components.ParametersEditor'; target element is 'Windows.UI.Xaml.Controls.TextBox' (Name='null'); target property is 'Text' (type 'String')
Error: BindingExpression path error: 'IsDefault' property not found on 'MyProject.ParametersViewModel'. BindingExpression: Path='ParameterSet.IsDefault' DataItem='MyProject.Components.ParametersEditor'; target element is 'Windows.UI.Xaml.Controls.TextBox' (Name='null'); target property is 'IsEnabled' (type 'Boolean')
...
These binding errors cause some delay during page load (more than 1 second, resulting in a terrible UX), and if I try to set a DataType on the DetailsTemplate, it crashes with a casting exception.
Here's the code behind :
public sealed partial class ParametersPage : Page
{
public ParametersPage()
{
this.InitializeComponent();
Loaded += ParametersPage_Loaded;
Unloaded += ParametersPage_Unloaded;
}
public ParametersViewModel ViewModel { get; set; } = new ParametersViewModel();
private async void ParametersPage_Loaded(object sender, RoutedEventArgs e)
{
await ViewModel.LoadParametersAsync();
}
private async void ParametersPage_Unloaded(object sender, RoutedEventArgs e)
{
await ViewModel.UpdateParameterSetAsync(ViewModel.Selected);
}
private async void ListViewParameters_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.RemovedItems.Count > 0 && e.RemovedItems[0] is ParameterSet parameters)
{
await ViewModel.UpdateParameterSetAsync(parameters);
}
}
}
And the ParametersViewModel looks like this :
public class ParametersViewModel : Observable
{
public ParametersViewModel()
{
ParametersList = new ObservableCollection<ParameterSet>();
}
public ObservableCollection<ParameterSet> ParametersList
{
get { return _parameters; }
set { Set(ref _parameters, value); }
}
// some other methods like LoadParametersAsync and UpdateParameterSetAsync
private ParameterSet _selected;
ParameterSet contains a string property Name and some other stuff.
Is there something wrong with my code? How can I avoid having the DataContext bound to this template?
Note: I also posted a question on the Windows Community Toolkit discussion on GitHub, but there's almost no traffic there.
Update: the crash when adding a DataType was happening because I didn't change {Binding} to {x:Bind}. It doesn't crash anymore, but there still is an exception logged in the debug console :
Exception thrown at 0x00007FF94A6F4B59 (KernelBase.dll) in MyApp.exe: WinRT originate error - 0x80004002 : 'System.InvalidCastException: Unable to cast object of type 'MyProject.ViewModels.ShellViewModel' to type 'MyProject.Core.Models.ParameterSet'.
at MyProject.Views.ParametersPage.ParametersPage_obj12_Bindings.SetDataRoot(Object newDataRoot)
at MyProject.Views.ParametersPage.ParametersPage_obj12_Bindings.DataContextChangedHandler(FrameworkElement sender, DataContextChangedEventArgs args)'.
Update 2: I forgot to mention that my project has been generated with Windows Template Studio and ShellViewModel is the general view model. The issue can be reproduced by generating a WTS project with one ListDetails page.
In ListDetailsPage.xaml, change the DetailsTemplate to this:
<DataTemplate x:Key="DetailsTemplate" x:DataType="model:SampleOrder">
<Grid>
<views:MasterDetailDetailControl MasterMenuItem="{x:Bind}" />
</Grid>
</DataTemplate>
A casting exception should be thrown.
The official document mentions that “Inside a DataTemplate, the value of Path is not interpreted in the context of the page, but in the context of the data object being templated. When using {x:Bind} in a data template, so that its bindings can be validated at compile-time, the DataTemplate needs to declare the type of its data object using x:DataType.”
<DataTemplate x:Key="SimpleItemTemplate" x:DataType="data:SampleDataGroup">
<StackPanel Orientation="Vertical" Height="50">
<TextBlock Text="{x:Bind Title}"/>
<TextBlock Text="{x:Bind Description}"/>
</StackPanel>
</DataTemplate>
As shown above, the example could be used as the ItemTemplate of an items control bound to a collection of SampleDataGroup objects.
In your scenario, you need to specify the x:DataType for the DataTemplate of ListDetailsView.DetailsTemplate, assuming that the type you specify is local:ParaClass class which contains the Name property. Then you need to bind the collection of ParaClass objects to the ListDetailsView ItemSource. Finally, you could use {x:Bind} to bind Name property in DataTemplate.
Update:
I have created a WTS project and reproduced your issue.
<views:ListDetailDetailControl ListMenuItem="{Binding}" />
As you can see, the ListDetailDetailControl binds the SampleOrder object. Therefore, its internal control could use <TextBlock Text="{x:Bind ListMenuItem.OrderDate, Mode=OneWay}" /> to achieve binding.
You changed the original template and placed these internal controls directly in DetailsTemplate, but you don’t set its binding object, so that these internal controls that use {Binding property} can’t find the correct DataContext.
Therefore, you need to set DataContext for these internal control, then you could use {Binding property} to set binding for these properties of internal controls directly. Please refer to the following code.
<DataTemplate x:Key="DetailsTemplate">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" DataContext="{Binding}">
<ScrollViewer..>
……
<TextBlock Style="{StaticResource DetailBodyBaseMediumStyle}" Text="{Binding Status, Mode=OneWay}" />
<TextBlock Style="{StaticResource DetailBodyBaseMediumStyle}" Text="{Binding OrderDate, Mode=OneWay}" />
<TextBlock Style="{StaticResource DetailBodyBaseMediumStyle}" Text="{Binding Company, Mode=OneWay}" />
……
</ScrollViewer>
</Grid>
</DataTemplate>

How do I Access Items of a Listbox

I have a Listbox done like this
<ListBox x:Name="lbAlbumSelect">
<ListBox.ItemTemplate>
<DataTemplate>
<Button>
<Button.Content>
<StackPanel>
<Image />
<TextBlock TextWrapping="Wrap"
Text="{Binding album_name}" />
</StackPanel>
</Button.Content>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I want to access every Image programmatically and set its Source. I tried to navigate the listbox like this
foreach (Button btn in lbAlbumSelect.Items)
{
StackPanel stack=btn.Content;
Image image=stack.Children.ElementAt(0) as Image;
//every ListBoxItem is binded to a clsAlbums object that contains various data,
//also the name of the image file, but not the path.
string pathImg = #"/Assets/Images/" + (btn.DataContext as clsAlbums).album_img;
LoadImage(pathImg, image); //function that sets image source to path img
}
But gives me a Invalid Cast Exception on the foreach clause.
Is there a faster and more correct way to do this?
You should ideally be binding the image source to the control. Add an additional property to your class clsAlbums which can be bound to the Image source.
public class clsAlbum
{
public string album_name { get; set; }
public string album_img { get; set; }
public string album_img_src
{
get
{
return #"/Assets/Images/" + album_img;
}
}
}
Now bind this additional property album_img_src to your xaml.
<ListBox x:Name="lbAlbumSelect">
<ListBox.ItemTemplate>
<DataTemplate>
<Button>
<Button.Content>
<StackPanel>
<Image Source="{Binding album_img_src}"/>
<TextBlock TextWrapping="Wrap"
Text="{Binding album_name}" />
</StackPanel>
</Button.Content>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The item is actually ListBoxItem. In your case Button is content of ListBoxItem and Content of Button is StackPanel and Image is child of StackPanel. So you need to traverse visual tree somehow and you can do so using Linq to visual tree, for example. http://www.codeproject.com/Articles/63157/LINQ-to-Visual-Tree
Probably easiest way of accessing elements inside datatemplate is from it's loaded or initialized event:
here:
<Image Loaded="Image_Loaded" />
void Image_Loaded(object sender, EventArgs e){
var image =(Image)sender;
Try to avoid acessing elements inside datatemplates. 90% times you achieve your goal better, using ViewModel, converters, using behaviours, datatriggers or extracting datatemplate to separate UserControl

WPF window not updating with binding in XAML

Can someone please explain what's going on here? I'm new to WPF and migrating my Forms project to WPF with binding. I'm using AvalonDock but I'm not binding directly to any of the AvalonDock controls. Here's a couple excerpts. I removed a lot for brevity's sake but let me know if you need to see something else.
EDIT: These two StackPanels are just tests... trying to figure this stuff out.
EDIT2: I'm trying to do MVVM eventually; I just need to get a better handle on binding so I know how to structure it.
EDIT3: See bottom of post.
Q: The first StackPanel does not update at all, never mind updating after changes. I've tried setting the DataContext in the StackPanel, Grid and TextBlock. What am I doing wrong?
Q: The second works fine when the parent grid is bound in code behind but only if bound where you see it, not in the MainWindow_Loaded() method. What's different here?
I've read several tutorials as well as plenty of similar questions here but nothing's helping me understand what the difference is here and what I'm missing.
<Window x:Class="TestUIWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ad="http://schemas.xceed.com/wpf/xaml/avalondock"
Title="MainWindow" Height="768" Width="1024"
Loaded="MainWindow_Loaded"
xmlns:vm="clr-namespace:TestUIWPF.ViewModel"
>
<!-- lots excluded for brevity. there are no Window.Resources -->
<ad:LayoutAnchorable Title="Test" >
<Grid x:Name="gridTest">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<StackPanel.DataContext>
<vm:EntityViewModel />
</StackPanel.DataContext>
<TextBlock Text="Label" />
<TextBlock DataContext="{Binding ActiveEntity}" Text="{Binding Path=Label}" />
</StackPanel>
<StackPanel Orientation="Horizontal" >
<TextBlock Text="Label Again" />
<TextBlock Text="{Binding Path=Label}" />
</StackPanel>
</StackPanel>
</Grid>
</ad:LayoutAnchorable>
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
this.DataContext = this;
SelectedEntityViewModel = new ViewModel.EntityViewModel();
ImportEntityXML_Click(null, null); //skips clicking the menus
}
private void ImportEntityXML_Click(object sender, RoutedEventArgs e)
{
//omitted OpenFileDialog and XmlReader stuff
xmlreader = new XmlReader(dlg.FileName);
Entities.Add(xmlreader.ReadEntityFromXML());
SimulatedEntitySelection(Entities.ElementAt(0)); //haven't built any of the UI stuff for this yet
}
private void SimulatedEntitySelection(Entity ent)
{
SelectedEntityViewModel.ActiveEntity = ent;
gridTest.DataContext = SelectedEntityViewModel.ActiveEntity;
}
private void button_Click(object sender, RoutedEventArgs e)
{
SelectedEntityViewModel.ActiveEntity.Label = "test";
}
Entity and EntityViewModel implement INotifyPropertyChanged and it works just fine with the second StackPanel. The button that calls button_Click() is just for testing the binding. EntityViewModel pretty much just wraps Entity through the ActiveEntity property and helps with reading the collections-of-collections within Entity.
EDIT3:
I've also tried a couple resources. Here's how I did the ObjectDataProvider:
<Window.Resources>
<ObjectDataProvider x:Key="testVM" ObjectType="{x:Type vm:EntityViewModel}" />
<vm:EntityViewModel x:Key="SelEntVM" />
</Window.Resources>
<!-- .... -->
<StackPanel.DataContext>
<Binding Source="{StaticResource testVM}" />
</StackPanel.DataContext>
<TextBlock Text="Label" />
<TextBlock Text="{Binding Path=ActiveEntity.Label}" />
Your first stack panel is not working, because the data context is inherited. Therefore, once you change the DataContext of the Grid to the ActiveEntity object, the binding on the text block in the first data context will set the datacontext for the TextBlock to the ActiveEntity on the current datacontext, which would already be the ActiveEntity (therefore ActiveEntity.ActiveEntity) And than try to bind to the Label property on that. E.g. ActiveEntity.ActiveEntity.Label
Before the click, you are setting the DataContext of that window to "this" which I am assuming is not the ViewModel, it is the code behind?
If you are using MVVM,
you should have something like this
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
SelectedEntityViewModel = new ViewModel.EntityViewModel();
this.DataContext = SelectedEntityViewModel;
ImportEntityXML_Click(null, null); //skips clicking the menus
}
Or some other ViewModel which provides all the necessary data.
You nomrally would have a MainWindowView and MainWindowViewModel, at least that is the convention and usually you set the datacontext of the window in the constructor once(you can do it in the Loaded handler), in most cases you shouldn't need to manually change the DataContext of any framework elements in the code behind.
EDIT: Example Code:
MainWindow.xaml
<Window x:Class="SO27760357.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="300" Width="300">
<Grid>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Label" />
<TextBlock Text="{Binding ActiveEntity.Label}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Label Again" />
<TextBlock Text="{Binding ActiveEntity.Label}" />
</StackPanel>
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
}
MainWindowViewModel.cs (INotifyPropertChanged omitted for simplicity)
public class MainWindowViewModel
{
public EntityViewModel ActiveEntity { get; set; }
}
EntityViewModel.cs (INotifyPropertChanged omitted for simplicity)
public class EntityViewModel
{
public string Label { get; set; }
}
As you can see, I am setting the DataContext of the Window to the MainViewModel, therefore the DataContext(root of all bindings) is the MainViewModel, and each TextBlock needs to first access the ActiveEntity property first, before it can get to the Label proeprty.
THe other option is, that if everything inside the main stack panel you given us, will be bound to ActiveEntity, you can change the DataContext of that StackPanel, binding it to the ActiveEntity, and therefore all of its children datacontext will also be that object.
<StackPanel Orientation="Vertical"
**DataContext="{Binding ActiveEntity}"**>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Label" />
<TextBlock **Text="{Binding Label}"**/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Label Again" />
<TextBlock **Text="{Binding Label}"** />
</StackPanel>
</StackPanel>
EDIT 2 - Advice
You should refrain from referencing objects by name as much as possible, and have as little logic in the code behind as possible, if any. For most simple screens there is no need to have anything in code behind other than the initial Binding of the DataContext (if you don't have a window service which creates + sets the DataContext of windows)
It does work. You might be probably updating a wrong viewmodel.
Once you define the viewmodel in DataContext, youd have to access it this way:
private void button_Click(object sender, RoutedEventArgs e)
{
var myModel = (ViewModel.EntityViewModel)(yourStackPanelName.DataContext);
myModel.ActiveEntity.Label = "test";
}

Drag Event Won't Fire for ListBox in Silverlight

Hopefully this is an easy question. I'm using the ListBoxDragDropTarget from the Silverlight Toolkit to set up drag and drop from one ListBox to another. I can't seem to get the event to fire. Here's my XAML code:
<toolkit:ListBoxDragDropTarget HorizontalAlignment="Left"
HorizontalContentAlignment="Stretch"
VerticalAlignment="Top"
VerticalContentAlignment="Stretch"
Margin="39,117,0,0"
Grid.Row="1"
AllowDrop='True'>
<ListBox x:Name='columnHeadings'
MinHeight='100'
MinWidth='100'>
</ListBox>
</toolkit:ListBoxDragDropTarget>
<toolkit:ListBoxDragDropTarget AllowDrop='True'
Grid.Column='1'
Grid.Row='1'
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
VerticalAlignment="Center"
HorizontalAlignment="Left">
<ListBox x:Name='customerFields'
Grid.Column='1'
Grid.Row='1'
Visibility='Collapsed'
Drop='CustomerFieldsDrop'>
</ListBox>
</toolkit:ListBoxDragDropTarget>
And here's my event handler in the code behind page:
private void CustomerFieldsDrop(object sender, DragEventArgs e)
{
MessageBox.Show(string.Format("Something was dropped!"));
}
As you can see, I was aiming for something real simple. I tried assigning an event handler to the parent ListBoxDragDropTarget for the customerFields List Box; ironically, that worked.
My purpose here is to allow a user to import a text file and get a listing of the file's column headers in one List Box and then "connect" them to data fields listed in the second List Box. So no list reordering or moving items from one list to another.
The columnHeadings.ItemsSource property is a string[] object. The customerFields.ItemsSource property is an IEnumerable<string> object.
Any insight would be appreciated.
I think the AllowDrop="True" and Drop="EventName" properties need to be within the same element to work. You probably have to set the AllowDrop property to "True" in the ListBox itself:
<ListBox x:Name="customerFields"
Grid.Column="1"
Grid.Row="1"
Visibility="Collapsed"
Drop="CustomerFieldsDrop"
AllowDrop="True"
</ListBox>
Or add the Drop="CustomerFieldsDrop" property to the ListBoxDragDropTarget tag:
<toolkit:ListBoxDragDropTarget AllowDrop='True'
Grid.Column='1'
Grid.Row='1'
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Drop="CustomerFieldsDrop">
Either one should work...

Use of DataTemplate

I'm quite new to C# and Windows Phone 7 for that sake, but none the less, I've thrown myself into trying to make a small app for myself. Here's my problem:
I'm trying to set up a DataTemplate that will position my Name and Drinks variables that I've declared in MainPage.xaml.cs. Here's my action when button1 is clicked:
private void button1_Click(object sender, RoutedEventArgs e)
{
string Name = participantName.Text;
int Drinks = 0;
listBox1.Items.Add(Name + Drinks);
}
And here is my DataTemplate from MainPage.xaml
<ListBox Height="Auto" HorizontalAlignment="Stretch" Margin="7,74,0,0" Name="listBox1" VerticalAlignment="Stretch" Width="Auto">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="132">
<TextBlock Text="{Binding Path=Name}" FontSize="35" />
<StackPanel Width="370">
<TextBlock Text="{Binding Path=Drinks}" FontSize="35" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The problem is that my data is not shown. It works perfectly without the DataTemplate, but as soon as I use it, my text simply doesn't get through. Your help is very much appreciated.
The template itself is ok. The bindings on the template, though, are currently incorrect.
When you add a new item to the list box, you are just adding a plain old string (which is currently missing a space, BTW.) Your bindings, though, expect the object in the list to have a Name property and a Drinks property, which of course the string class does not have.
The usual solution here is to logically separate your data model from your presentation, by creating a class to store the data itself (probably PersonDrink, with the appropriate Name and Drinks properties) and then adding those objects to the list.
You should read up on the MVVM pattern, as it provides an excellent way to ensure that changes in your data are reflected in your view, and visa versa.
http://amarchandra.wordpress.com/2011/12/18/binding-multiple-object-in-wp7-using-listbox/
Here is a sample for binding data using a datatemplate. I hope this might help you.

Categories