I'm trying to display different layouts in pivot items in a wp8.1 app (UNIAPP ). Ideally I would like to load different pages but since I could figure this out, I thought I'd try with the basics first as I'd use this before but for some reason I can't get this to work.
My pivot items are loaded dynamically based on the provided ViewModel
<Pivot.ItemTemplate>
<DataTemplate>
<controls:DataTemplateSelector Content="{Binding}"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch">
</controls:DataTemplateSelector>
/DataTemplate>
</Pivot.ItemTemplate>
My resources are defined as follows within the same xaml page
<Page.Resources>
<DataTemplate x:Key="MyApp.ViewModel.PIDetailsVM">
<Button Content="test" Foreground="White"></Button>
</DataTemplate>
<DataTemplate x:Key="MyApp.ViewModel.PIListVM">
<Button Content="test" Foreground="White"></Button>
</DataTemplate>
</Page.Resources>
My DataTemplateSelector is defined as follows:
public class DataTemplateSelector : ContentControl
{
protected override void OnContentChanged(object oldContent,
object newContent)
{
ContentTemplate = this.FindResource<DataTemplate>(newContent.GetType
().FullName);
}
}
It is being triggered whenever I go to a new pivot item, but the ContentTemplate is always null.
The newContent.GetType().FullName returns the relevant viewmodel name which I can see being displayed in the relevant pivot.
One thing I noticed is that the DataTemplateSelector class (this) has no resources when I check it via this.Resources.count(), so it's obviously not finding them but how do I fix this?
UPDATE:
My DataTemplates are not getting loaded in my Pivot Items. There is obviously a problem with the .NET IDE as whenever I add or remove a from Content="{Binding}" it displays the button within the pivot item but that's within the IDE. Unfortunately, at run-time, it just displays the name of my viewmodel.
Thought the behaviour is erratic in the IDE, the fact that the button from my DataTemplate is displaying when messing around with the Content="{Binding<space>" would make you think that the code and xaml are correct but it's definitely not working at run-time.
Any idea what's wrong why my DataTemplates are not displaying in pivot item?
Thanks.
This is a partial answer. By this I mean that I did find a work-around to my problem but I did not resolve the issue itself.
My DataTemplateSelector which gets triggered whenever the pivot changes call a extension function called FindResource:
public static class ControlExtensions
{
public static T FindResource<T>(this DependencyObject initial,
string key) where T : DependencyObject
{
DependencyObject current = initial;
while (current != null)
{
if (current is FrameworkElement)
{
if ((current as FrameworkElement).Resources.
ContainsKey(key))
{
return (T)(current as FrameworkElement).Resources[key];
}
}
current = VisualTreeHelper.GetParent(current);
}
if (Application.Current.Resources.ContainsKey(key))
{
return (T)Application.Current.Resources[key];
}
return default(T);
}
}
For some strange reason, Windows Phone 8.1 (WinRT) does not like having the data templates in while it is not a problem in WP8/WP8.1 Silverlight.
As mentioned, this is unstable in the IDE where it sometimes displays the DataTemplate, and sometimes it doesn't depending on whether or not I add a space after the Binding keyword to the Content="{Binding}". One thing for sure is that it never works at run-time, well not at least not with the above code.
VisualTreeHelper.GetParent(current) always returns null no matter what. I've checked at debug time if I somehow could access the resources, but to no avail.
How did I fix it? Well, I moved my data templates to a resource dictionary
<DataTemplate x:Key="MyApp.ViewModel.PIDetailsVM">
<Button Content="test" Foreground="White"></Button>
</DataTemplate>
<DataTemplate x:Key="MyApp.ViewModel.PIListVM">
<Button Content="test" Foreground="White"></Button>
</DataTemplate>
The second I did this, the second part of my FindResources kicks in since the Current object is always null, no matter what
if (Application.Current.Resources.ContainsKey(key))
{
return (T)Application.Current.Resources[key];
}
and it finds the relevant DataTemplate and displays it accordingly in my pivot control based on the relevant PivotItem ViewModel.
Now, I'm not out of the woods yet as I have no idea if binding to the relevant viewmodel will work but that's a whole other story!
If anyone knows why DataTemplate cannot be found when defined in Pages.Resources or Grid.Resources, please update the post as I'd love to know why.
Thanks.
Related
I've been playing around with WPF and MVVM and noticed a strange thing. When using {Binding ElementName=...} on a custom user control, the name of the root element within the user control seems to be visible in the window using the control. Say, here is an example user control:
<UserControl x:Class="TryWPF.EmployeeControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TryWPF"
Name="root">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding}"/>
<Button Grid.Column="1" Content="Delete"
Command="{Binding DeleteEmployee, ElementName=root}"
CommandParameter="{Binding}"/>
</Grid>
</UserControl>
Looks pretty legit to me. Now, the dependency property DeleteEmployee is defined in the code-behind, like this:
public partial class EmployeeControl : UserControl
{
public static DependencyProperty DeleteEmployeeProperty
= DependencyProperty.Register("DeleteEmployee",
typeof(ICommand),
typeof(EmployeeControl));
public EmployeeControl()
{
InitializeComponent();
}
public ICommand DeleteEmployee
{
get
{
return (ICommand)GetValue(DeleteEmployeeProperty);
}
set
{
SetValue(DeleteEmployeeProperty, value);
}
}
}
Nothing mysterious here. Then, the window using the control looks like this:
<Window x:Class="TryWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TryWPF"
Name="root"
Title="Try WPF!" Height="350" Width="525">
<StackPanel>
<ListBox ItemsSource="{Binding Employees}" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<local:EmployeeControl
HorizontalAlignment="Stretch"
DeleteEmployee="{Binding DataContext.DeleteEmployee, ElementName=root}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Window>
Again, nothing fancy... except the fact that both the window and the user control have the same name! But I'd expect root to mean the same thing throughout the whole window XAML file, and therefore refer to the window, not to the user control. Alas, the following message is printed when I run it:
System.Windows.Data Error: 40 : BindingExpression path error:
'DeleteEmployee' property not found on 'object' ''String'
(HashCode=-843597893)'.
BindingExpression:Path=DataContext.DeleteEmployee;
DataItem='EmployeeControl' (Name='root'); target element is
'EmployeeControl' (Name='root'); target property is 'DeleteEmployee'
(type 'ICommand')
DataItem='EmployeeControl' (Name='root') makes me think that it treats ElementName=root as referring to the control itself. The fact that it looks for DeleteEmployee on string confirms that suspicion because string is exactly what the data context is in my contrived VM. Here it is, for the sake of completeness:
class ViewModel
{
public ObservableCollection<string> Employees { get; private set; }
public ICommand DeleteEmployee { get; private set; }
public ViewModel()
{
Employees = new ObservableCollection<string>();
Employees.Add("e1");
Employees.Add("e2");
Employees.Add("e3");
DeleteEmployee = new DelegateCommand<string>(OnDeleteEmployee);
}
private void OnDeleteEmployee(string employee)
{
Employees.Remove(employee);
}
}
It is instantiated and assigned to the window in the constructor, which is the only thing in code-behind for the window:
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
This phenomenon prompts the following questions:
Is this by design?
If so, how is someone using a custom control supposed to know what name it uses internally?
If Name is not supposed to be used in custom control at all?
If so, then what are the alternatives? I switched to using {RelativeSource} in FindAncestor mode, which is working fine, but are there better ways?
Does this have anything to do with the fact that data templates define their own names copes? It doesn't stop me from referring to the main window from within a template if I just rename it so the name doesn't clash with the control.
Your confusion here about how wpf namescopes work is understanable in this situation.
Your issue is simply that you are applying a binding upon a UserControl, which is the "root" (so to speak) of its own namescope. UserControls, and pretty much any container objects, have their own namescopes. These scopes encompass not only child elements, but the object that contains the namescope as well. This is why you can apply x:Name="root" to your window and (except in this one case) locate it from a child control. If you couldn't, namescopes would be pretty much useless.
The confusion comes when you're acting upon a root of a namescope within an encompassing namescope. Your assumption was that the parent's namescope had precedence, but it does not. The Binding is calling FindName on the target object, which in your case is your user control. (Side note, the Binding isn't doing jack, the actual calls can be found in ElementObjectRef.GetObject, but that's where the Binding delegates the call to)
When you call FindName on the root of a namescope, only names defined within this scope are examined. Parent scopes are not searched. (Edit... a bit more reading of the source http://referencesource.microsoft.com/#PresentationFramework/src/Framework/MS/Internal/Data/ObjectRef.cs,5a01adbbb94284c0 starting at line 46 I see that the algorithm walks up the visual tree until it finds a target, so child scopes have precedence over parent scopes)
The result of all this is that you get the user control instance instead of the window, like you were hoping. Now, to answer your individual questions...
1. Is this by design?
Yep. Otherwise namescopes wouldn't work.
2. If so, how is someone using a custom control supposed to know what name it uses internally?
Ideally, you wouldn't. Just like you don't ever want to have to know the name of the root of a TextBox. Interestingly, though, knowing the names of templates defined within a control is often important when attempting to modify it's look and feel...
3. If Name is not supposed to be used in custom control at all?
If so, then what are the alternatives? I switched to using {RelativeSource} in FindAncestor mode, which is working fine, but are there better ways?
No! It's fine. Use it. If you aren't sharing your UserControl with other people, just make sure to change its name if you are experiencing this particular problem. If you aren't having any problem, reuse the same name all day, it isn't hurting anything.
If you ARE sharing your UserControl, you should probably rename it to something that won't conflict with other people's names. Call it MuhUserControlTypeName_MuhRoot_Durr or something.
4. If so, then what are the alternatives? I switched to using {RelativeSource} in FindAncestor mode, which is working fine, but are there better ways?
Nah. Just change the x:Name of your user control and move on.
5. Does this have anything to do with the fact that data templates define their own names copes? It doesn't stop me from referring to the main window from within a template if I just rename it so the name doesn't clash with the control.
No, I don't believe so. I don't think there is any good reason for it to be, anyhow.
I tried to follow the example here:
WPF ListBox with self-removing items
It made sense but my issue was, the ListView itself is determining the template used. So it can easily customise the bindings to point to the correct target. I am however using MVVM and am struggling to fit the two together.
Example, if the template was:
<ListBox.ItemTemplate>
<DataTemplate>
<local:MyItemView/>
</DataTemplate>
</ListBox.ItemTemplate>
This suddenly becomes more difficult, as ideally, I want to reuse that view without hard coding the bindings.
I tried to use DependencyProperty to pass the List and the Element through, so I could delete it via command.
<ListBox.ItemTemplate Name="myList">
<DataTemplate>
<local:MyItemView TheList={Binding ElementName=myList, Path=DataContext.List} TheElement={Binding}/>
</DataTemplate>
</ListBox.ItemTemplate>
However, I had binding errors telling me that it couldn't convert the value for TheElement from MyClassViewModel to MyClass. Even if I commented that out TheList was always NULL.
Essentially I want:
class MyDataClass { // pretend there's more here}
class MyDataClassContainer
{
public ObservableCollection<MyDataClass> Items;
public void Add(MyDataClass);
public void Remove(MyDataClass);
}
class MyDataClassEntryViewModel
{
public static readonly DependencyProperty ListItemProperty = DependencyProperty.Register("TheClass", typeof(MyDataClass), typeof(MyDataClassEntryViewModel));
public static readonly DependencyProperty ListContainerProperty = DependencyProperty.Register("TheContainer", typeof(MyDataClassContainer), typeof(MyDataClassEntryViewModel));
public MyDataClass TheClass;
public MyDataClassContainer TheContainer;
public ICommand Delete = new DelegateCommand(RemoveItem);
private function RemoveItem(object parameter)
{
TheContainer.Remove(TheClass);
}
}
With the following templates:
MyDataClassEntryView.xaml
<UserControl>
<Grid>
<Button Content="Delete" Command="{Binding Path=Delete}"/>
</Grid>
</UserControl>
MyDataContainerView.xaml
<UserControl>
<ListView x:Name="listView" ItemsSource="{Binding Path=Container.Items}">
<ListView.ItemTemplate>
<DataTemplate>
<local:MyDataClassEntryView TheClass="{Binding}" TheContainer="{Binding ElementName=listView, Path=DataContext.Container}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</UserControl>
Note: I have omitted most of the superfluous lines, as I'm trying to get a generic answer I can use everywhere. Not a hard coded single solution. I was basically want to keep the MVVM structure strong, without lots of hard coded and wiring in the background. I want to use the XAML as much as possible.
All the other methods I see to do with removing from a list, require all sorts of assumptions, such as using the SelectedIndex/Item, or using a method on the ContainerView itself to take the element as a parameter, cast it, then remove, etc. In short, most solutions are far too hard coded to the given examples. It feels like there should be an easy way to achieve this in WPF.
As the ListView issautomatically creating instances of my sub-ViewModel/Views, it's impossible for me to get any data in apparently. I just want to pass parameters along using bindings, basically.
Your button should look like this:
<Button Content="Delete"
Command="{Binding Path=Delete}"
CommandParameter="{Binding}/>
Then the remove command should look something like this:
private function RemoveItem(object parameter)
{
var item = parameter as MyDataClass
if(item != null)
TheContainer.Remove(item);
}
You do not need to pass the list to the UserControl within the ItemTemplate, since it doesn't need to know about the list at all
Edit:
I read over your question a few times to see what you were confused about so I will try to clarify.
Whether the ListView sets its own template in the Xaml, or you use another UserControl, the datacontext still gets passed down to the item. Regardless of how you decide to template the items, the ItemTemplate will have the datacontext of a single item from the ListView's items list.
I think your confusion comes in with having controls outside being brought in for templating. Think of it as if the Xaml from the control you brought in being cut and pasted into the DataTemplate of the ListView when running the program, and then it is really no different from being hard coded in there.
You cannot reach outside of a DataTemplate with Element bindings like you have tried.
Instead you need to use a relativesource like this.
<local:MyItemView TheList="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListBox}, Path=DataContext.List}" />
I'm having a rather frustrating problem here... I have a WPF Page that contains a TabControl, and the content of the various TabItems is another WPF Page (hosted in a Frame because Page can only have Frame or Window as a parent). Even though the FlowCalibrationSummaryView is being displayed, everything on it is empty because the data binding of the SummaryViewModel is not working for some reason. Here's part of the XAML:
<TabControl Grid.Row="0">
<TabItem Header="Current Calibration">
<TabItem.Content>
<Frame>
<Frame.Content>
<view:FlowCalibrationSummaryView DataContext="{Binding SummaryViewModel}"/>
</Frame.Content>
</Frame>
</TabItem.Content>
</TabItem>
</TabControl>
I have a break point on the get of SummaryViewModel, and it is only getting hit by the code that is constructing it in the parent view model. Here's the property being bound to:
public const string SummaryViewModelPropertyName = "SummaryViewModel";
private FlowCalibrationSummaryViewModel _SummaryViewModel;
public FlowCalibrationSummaryViewModel SummaryViewModel
{
get { return _SummaryViewModel; }
set
{
if (_SummaryViewModel == value)
return;
_SummaryViewModel = value;
RaisePropertyChanged(SummaryViewModelPropertyName);
}
}
At this point I'm completely stumped, I cannot for the life of me figure out why this binding is not working. Any help would be much appreciated.
Update: It definitely has something to do with it being in a Frame. I tried changing the FlowCalibrationSummaryView to a UserControl instead of a Page to see if that helped, and it didn't, then I tried taking it out of the Frame it was wrapped in and that worked. All of the views in my project are done as Pages so for consistency's sake I'd prefer this to be a Page, but then I have to host it in a Frame. Is there something I'm missing here, or is the proper usage to do it as a UserControl?
I would take a look at the answer provided in this question. It seems to provide a specific explanation of the behavior of Frame in this case.
Unless there is a specific reason you have chosen Frame over UserControl, you would be better off re-implementing this (and any other sub-view) as UserControl instead.
So, I have made my own subclass of UserControl, called ChildView (I really can't come up with a decent name), that I want to show inside a container in a window, I have many different kinds of these UserControls and the window must be capable of showing all of them. The UserControls have implemented my subclass like this:
<src:ChildView x:Class="(namespace).LoginView" [...]>
public partial class LoginView : ChildView
And I have tried to add it to my window like so:
<Grid x:Name="ViewHolder" Grid.Column="1" Grid.Row="1">
<src:ChildView DataContext="{Binding CurrentView}" />
</Grid>
private ChildView _currentView;
public ChildView CurrentView
{
get { return _currentView; }
set
{
if (_currentView == value)
return;
_currentView = value;
smLog.Trace("View set to {0}", value.GetType().Name);
NotifyPropertyChanged("CurrentView");
}
}
However, this does not work. Nothing is shown in my container when I set CurrentView. There are no error messages in the output that would indicate a problem with the binding. Other data bindings in the window works. I can use my ChildViews by specifying their classes directly in the XAML, i.e:
<Grid x:Name="ViewHolder" Grid.Column="1" Grid.Row="1">
<src:LoginView />
</Grid>
I've read some about dependency properties but I don't think I need one here? I did try to implement one anyway but it didn't seem to help, though I probably made some mistake, I couldn't quite wrap my head around it...
So I guess my question is; do I need a dependency property? If so, how do I implement it in this case? If not, what is the problem?
Changing the Child's DataContext won't matter, you're trying to change the control itself, not the data it's bound to. What you need to do is add a placeholder control that would contain the actual view. WPF has such a thing built in, take a look at ContentControl.
Change your grid so it'll containt a ContentControl instead of ChildView, and bind the view to the control's Content property
<Grid>
<ContentControl Content="{Binding CurrentView}"/>
</Grid>
i have a datatemplate declared in xaml.
for e.g.
<DataTemplate x:Key="TestTemplate">
<StackPanel>
<TextBox Name="txtBox" Visibility="Visible"></TextBox>
</StackPanel>
</DataTemplate>
I wish to set the binding for txtBox in code behind before the element is generated because i have different binding paths for different elements that get generated
I can get the template in the code behind as :
DataTemplate tmplt = FindResource("TestTemplate") as DataTemplate;
but i am not sure what to do next. How to get the the txtBox reference to set the binding.
We have to remember one thing that Templates are not instantiated UI controls. They are streamed obejcts in XAML and are shared between UI elements. So if you edit a dataTemplate and change its stucture (by adding, editing, deleting an element under the template) it would change the one data template which is shared among controls. Thus other elements using that template will also be affected by the change.
Now lets address your issue of adding a dynamic biding to a textbox. You say each generated textbox will have different binding paths. So this definitely does NOT call for changing the data template itself!
You will have to access the text box and add dynamic bindings to it AFTER the textbox's is generated.
I see that your binding differs based on your "situation", so why cant you use TemplateSelector? Template selector will decide which data template (having one specific binding applied to the TetxBox) at runtime.
The first part of answer - is FindName() method.
example:
DataTemplate tmplt = FindResource("TestTemplate") as DataTemplate;
TextBox my = (TextBox)tmplt.FindName("txtBox");
try out this, it should help to get access to TextBox control. I think that you know how to bind to. If you want your DataBinding behave different way, use MultiBinding and Converter.
EDIT
public class GeneralObject
{
private object someObject;
public GeneralObject(object initObject)
{
this.someObject = initObject;
}
//If you want to bind to some text, for example
public string Text
{
get
{
//I think you know which objects are coming as input
if (this.someObject is SpecialClass1)
return ((SpecialClass1)this.someObject).SpecialClass1TextProperty;
if (this.someObject is SpecialClass2)
return ((SpecialClass2)this.someObject).SpecialClass2TextProperty;
//and so on.
}
}
}
EDIT 2
One more possible way
So I remember, that WPF have ContentControl!
<ContentControl Content="{Binding Path=CurrentObject}"/>
But in this case you have to create number of DataTemplate's, every Template for one class.
<DataTemplate DataType="{x:Type local:SpecialClass1}">
...
</DataTemplate>
<DataTemplate DataType="{x:Type local:SpecialClass2}">
...
</DataTemplate>
<!--and so on-->
WPF resolve DataTypes of ContentControl.Content property, and put to the ContentControl right DataTemplate.