StoryBoard.Begin on ItemControl Items? - wpf

I have an ItemsControl, and its ItemsSource is an ObservableCollection. I also have a DataTemplate to display each MyPerson.
<ItemsControl Name="ic_People" ItemTemplate="{StaticResource dtPerson}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
The DataTemplate itself is a bit complex, but part of it is an Image.
In codebehind I'd like to animate some/all of these DataTemplates, by moving the Image (using Canvas.Top etc).
My codebehind got stuck here;
Storyboard sb = new Storyboard();
DoubleAnimation da = new DoubleAnimation();
da.From = 0;
da.By = 68;
da.Duration = new Duration(TimeSpan.FromSeconds(.2));
sb.Children.Add(da);
object prop = Canvas.TopProperty;
Storyboard.SetTargetProperty(da, new PropertyPath(prop));
sb.Begin(???, true); <---- needs a FrameworkElement
I have access to the ObservableCollection. How do I get access to DataTemplate or Person as a FrameworkElement (which is what the Begin method requires)?
thanks

Related

Changing content changes DataContext when using DataTemplate

X problem.
I want to enlarge (let it take whole available space) a part of window content temporarily.
Window layout is pretty complicated: many nested panels, splitters, content to enlarge is 10 levels deep. Changing Visibility to stretch content is simply not enough (thanks to splitters) and seems very complicated.
Y problem
I decide to move that content into a user control and do something like (pseudo-code)
if(IsEnlarged)
{
oldContent = window.Content; // store
window.Content = newContent;
}
else
window.Content = oldContent; // restore
No problems. It was working perfectly in test project ... until I start using data templates.
Problem: if data templates are used then as soon as window.Content = newContent occurs, then that newContent.DataContext is lost and become the same as window.DataContext. This will trigger various binding errors, attached behaviors suddenly changes to default value, etc.. all kind of bad stuff.
Question: why DataContext is changing? How to prevent/fix this issue?
Here is a repro (sorry, can't make it any shorter):
MainWindow.xaml contains
<Window.Resources>
<DataTemplate DataType="{x:Type local:ViewModel}">
<local:UserControl1 />
</DataTemplate>
</Window.Resources>
<Grid Background="Gray">
<ContentControl Content="{Binding ViewModel}" />
</Grid>
MainWindow.cs contains
public ViewModel ViewModel { get; } = new ViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
UserControl1.xaml contains
<Button Width="100"
Height="100"
CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
Command="{Binding Command}" />
ViewModel (using DelegateCommand)
public class ViewModel
{
public DelegateCommand Command { get; set; }
bool _set;
object _content;
public ViewModel()
{
Command = new DelegateCommand(o =>
{
var button = (Button)o;
var window = Window.GetWindow(button);
_set = !_set;
if (_set)
{
_content = window.Content;
var a = button.DataContext; // a == ViewModel
window.Content = button;
var b = button.DataContext; // b == MainWindow ??? why???
}
else
window.Content = _content;
});
}
}
Set breakpoint on var a = ..., start program, click the button, do steps and observe button.DataContext as well as binding error in Output window.
ok here some general thoughts.
if you bind a object(viewmodel) to the Content property of the contentcontrol then wpf uses a DataTemplate to visualize the object. if you dont have a DataTemplate you just see object.ToString(). DataContext inheritance means that the DataContext is inherited to the child elements. so a real usercontrol will inherit the DataContext from the parent. the are common mistakes you find here on stackoverflow when creating UserControls - they often break DataContext inheritance and set the DataContext to self or a new DataContext.
UserControl DataContext Binding
You must be trying to use your DataTemplate as ContentTemplate for your ContentControl. As ContentTemplate operates on Content, so it will use Content as its DataContext. And your Content contains ViewModel.
Once your Button is no longer part of your DataTemplate, so it will use MainWindow's DataContext.
No seeing your comments, I am assuming that you want DataContext of UserControl to remain intact, even if your UserControl is not part of DataTemplate.
So, simple set DataContext of Button explicitly using XAML using RelativeSource.
Example,
<Button Content="{Binding Data}" DataContext="{Binding vm1, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" />
Playing with DataContext in code is not a good idea.

Set Binding for DataTemplate's elements programmatically

I have such a template
<Window.Resources>
<DataTemplate x:Key="MemberCoefDataTemplate">
<StackPanel>
<CheckBox Name="CheckBox"></CheckBox>
<TextBox Name="TextBox"></TextBox>
</StackPanel>
</DataTemplate>
</Window.Resources>
And i use it in a grid
<DataGridTemplateColumn CellTemplate="{StaticResource MemberCoefDataTemplate}" />
I need to dynamically add columns to the grid. As Binding i use indexer property. So i need to set binding dynamically, because i don't know to which index bind to. When i just for test tried this
var column = new DataGridTemplateColumn();
column.CellTemplate = (DataTemplate)Application.Current.MainWindow.Resources["MemberCoefDataTemplate"];
TextBox tb = column.CellTemplate.FindName("TextBox", dg) as TextBox;
I got InvalidOperationException wit description:
this operation is valid only on elements that have this template applied
Usually to get the control during the setup time, you would have to call the method LoadContent();
I would therefore try column.CellTemplate.LoadContent().
You can find a full explanation about your problem (and a solution of course) written by Josh Smith here on his blog.
Indeed this is the point where the DataGridTemplateColumn applies the DataTemplate:
private FrameworkElement LoadTemplateContent(bool isEditing, object dataItem, DataGridCell cell)
{
DataTemplate dataTemplate;
DataTemplateSelector dataTemplateSelector;
this.ChooseCellTemplateAndSelector(isEditing, out dataTemplate, out dataTemplateSelector);
if (dataTemplate != null || dataTemplateSelector != null)
{
ContentPresenter contentPresenter = new ContentPresenter();
BindingOperations.SetBinding(contentPresenter, ContentPresenter.ContentProperty, new Binding());
contentPresenter.ContentTemplate = dataTemplate;
contentPresenter.ContentTemplateSelector = dataTemplateSelector;
return contentPresenter;
}
return null;
}
As you can see a ContentPresenter is used.

Binding dynamically generated textblock WPF MVVM Light

I am using MVVMM Light WPF and I want to do the following: Generate textboxes dynamically and bind them to a property of a class.
I already have the following but it doesn't show up in my view when running the application.
This is my collection:
private ObservableCollection<Border> _controllekes;
public ObservableCollection<Border> Controllekes
{
get { return _controllekes; }
set
{
_controllekes = value;
RaisePropertyChanged("Controllekes");
}
}
This it my xaml:
<ItemsControl ItemsSource="{Binding Path=Controllekes}">
</ItemsControl>
This is a part where I fill the itemsource "Controllekes":
Controllekes = new ObservableCollection<Border>();
Border border = new Border();
border.BorderThickness = new System.Windows.Thickness(5);
border.BorderBrush = Brushes.AliceBlue;
border.Padding = new System.Windows.Thickness(5);
TextBlock tb = new TextBlock();
tb.Background = Brushes.Red;
Binding nameTextBinding = new Binding("Controllekes");
nameTextBinding.Path = new System.Windows.PropertyPath(this.Dossier.Omschrijving);
nameTextBinding.Mode = BindingMode.OneWay;
//nameTextBinding.Source = this.Dossier.Omschrijving;
tb.SetBinding(TextBlock.TextProperty, nameTextBinding);
border.Child = tb;
this.Controllekes.Add(border);
What it does it creates a border with in this border a textblock where the binding should happen. I whish to bind the property this.Dossier.Omschrijving (Dossier is the class). If I just enter a string in the textbox it works.
In runtime the border gets generated but the textblock remains empty. The object Dossier.Omschrijving contains information.
What do I do wrong?
EDIT:
safe put me in the right direction and the answer of ItemsControl with multiple DataTemplates for a viewmodel made me finish the job :)
through all that and use ItemTemplate
<ItemsControl x:Name="ic">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<CheckBox IsChecked="{Binding yourboolProperty}"/>
<TextBlock Background="Red" Text="{Binding yourStringProperty}"></TextBlock>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
and set ic's ItemsSource to List

Hit testing bound item in ItemsControl?

I have an ItemsControl bound to a list, MyItems, with objects of SomeType. When I click on my UI elements (i.e. ellipses), I want to get hold of the SomeType object.
This does NOT work:
public HitTestResultBehavior SomeTypeHitCallback(HitTestResult result)
{
if (result.VisualHit is Ellipse)
{
var ellipse = result.VisualHit as Ellipse;
// Does not work...
object item = itemsSource.ItemContainerGenerator.ItemFromContainer(ellipse);
// item now equals DependencyProperty.UnsetValue
// Here I want to change the property of the object
// associated with the Ellipse...
var o = item as SomeType;
o.IsSelected = !o.IsSelected;
return HitTestResultBehavior.Continue;
}
return HitTestResultBehavior.Stop;
}
private void Canvas_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var pt = e.GetPosition((UIElement)sender);
VisualTreeHelper.HitTest(
(UIElement)sender,
null,
new HitTestResultCallback(SomeTypeHitCallback),
new PointHitTestParameters(pt));
}
Here's the XAML:
<ItemsControl x:Name="itemsSource" ItemsSource="{Binding Path=MyItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas ClipToBounds="True" PreviewMouseLeftButtonDown="Canvas_PreviewMouseLeftButtonDown" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse x:Name="item" Width="{Binding Width}" Height="{Binding Height}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
How can I find the SomeType object?
itemsSource.ItemContainerGenerator.ItemFromContainer will only work if you pass Item Container, but not visual elements of it. So you need to find ContentPresenter that contains the Ellipse, and pass that as argument to ItemFromContainer method. Since ItemsContainer for ItemsControl is ContentPresenter.
One way I see, is to go up by parents from VisualHit, until you find ContentPresenter, and call ItemFromContainer for that item. Try this, it should work. But the problem here might be that ContentPresenter may exist inside the template of ItemsContainer, and you will get null again. Definitely by chaning ItemsControl to ListBox will make easier to find ListBoxItem, but you will have to re-style it and remove additional features you don't require.
Also try to check Ellipse.DataContext, I could be exactly what you want

Setting multiple Datacontext

I am trying to find out how to set correctly multiple DataContexts in XAML page. I have a basic collection that I create in code behind and set ItemSource Binding og AutoCompleteBox to it. At the same time, I have another datacontext to set labelsDataSource inside the grid. If I set this datacontext, the AutoCompleteBox’s itemsSource binding is lost. AutoCompleteBox is inside that grid. I do assign DataContext directly to the objetc this way:
MyAutoCompleteBox.DataContext = this;
I am wondering if there is a better way to do it?
Thank you in advance for the help!
Setting AutoComplete Box:
<sdk:AutoCompleteBox x:Name="MyAutoCompleteBox" IsTextCompletionEnabled="True" ItemsSource="{Binding Items}" />
Code Behind:
public IList<string> Items
{
get;
private set;
}
public Basic_ChildWindow()
{
InitializeComponent();
Items = new List<string>();
Items.Add(#"One");
Items.Add(#"Two");
Items.Add(#"Three");
DataContext = this;
}
Another datacontext in the same XAML page, AutoCompleteBox is inside that grid:
<Grid x:Name="grdBasic_ChildWindow_Right" Style="{StaticResource GridStyle}" DataContext="{Binding Source={StaticResource LabelsDataSource}}">
I'm not sure I understand your question--what is "labelsDataSource"?
However, if what you have posted is all the code and there is nothing more to it, simply remove the datacontext/binding from the grid. The grid does not need a datacontext set (it is simply a visual container--not data-related).
So change this:
<Grid x:Name="grdBasic_ChildWindow_Right" Style="{StaticResource GridStyle}" DataContext="{Binding Source={StaticResource LabelsDataSource}}">
To this:
<Grid x:Name="grdBasic_ChildWindow_Right" Style="{StaticResource GridStyle}">

Resources