Bindings not applied to dynamically-loaded xaml - c#

I'm using XamlReader successfully to load a xaml file and create a FrameworkElement to work with.
The xaml I'm loading has binding expressions in it such as:
<TextBlock Text="{Binding DataContextTextProperty}" />
If I place the FrameworkElement I get back from XamlReader.Load() into a WPF window, the binding all works fine.
However, in this case I'm using Laurent Bugnion's excellent article on creating PNGs from WPF/XAML. Since the result of XamlReader.Load() is written directly to a PNG via a VisualBrush, it seems the necessary mechanics of WPF to invoke binding expressions are bypassed.
This leads me to believe that the actual bindings aren't really being invoked just by calling XamlReader.Load(), or that they're not working because of something I don't know about to do with there not being a visual tree until you add the FrameworkElement to an existing visual tree or something.
Is there something I can do to ensure these bindings are invoked?
Many thanks in advance.

I FIXED IT!!
Ahem, allow me to explain...
I have no idea how I got to it now, but I found a helpful-sounding article on MSDN regarding Initialization for Objects Not in an Object Tree.
In it I found the following code example:
Button b = new Button();
b.BeginInit();
b.Background = Brushes.Blue;
b.Width = b.Height = 200;
b.EndInit();
b.Measure(paperSize);
b.Arrange(new Rect(paperSize));
b.UpdateLayout();
I looked at the (again, excellent) example from Laurent that I mentioned in the question above, and customised the use of XamlReader as follows:
var element = (FrameworkElement)XamlReader.Load(xamlInput);
element.BeginInit();
element.DataContext = dataContext;
...
element.Measure(renderingSize);
element.Arrange(renderingRectangle);
element.EndInit();
element.UpdateLayout();
I added the BeginInit(), EndInit() and UpdateLayout() (though by process of elimination I believe UpdateLayout() is the key) and now the binding expressions in my dynamically-loaded xaml are working correctly. Hurrah!

Related

C# / WPF - Getting object inside a child user control from parent window

Not terribly familiar with WPF and C# so if this is blatantly wrong, please correct me. Working in VSExpress2015 .NET Framework 4.5. I'm heavily simplifying my code below, so know that namespace/library references are there.
Say I have a window with a Button and ContentControl inside:
<Window x:Class="Project.MainWindow">
<Grid>
<Button Name="Submit_Btn" Click="Submit_Btn_Click">
<ContentControl Name="MainContentControl">
</Grid>
</Window>
I also have several user control files in my project with XAML that looks something like this:
<UserControl x:Class="Project.UserControl1">
<Grid>
<TextBox Name="TxtBox1">
</Grid>
</Usercontrol>
I have code in the backend of my MainWindow to dynamically load the appropriate UserControl into the "MainContentControl" object. However, I want to reference the objects inside of the currently loaded UserControl from the MainWindow's Submit_Btn_Click function. For example, in MainWindow.cs, do something like this:
private void Submit_Btn_Click(object sender, RoutedEventArgs e)
{
if(MainContentControl is currently loaded with UserControl1)
Do_Something_Function(MainContentControl.TxtBox1.Text);
}
The main problem here is I don't know how to call the TextBox1 element from within the parent MainWindow's scope. I'm also not sure how to validate the if condition (confirming the control is currently loaded). Does anyone know of a way to think about this differently or even directly reference the object (despite that probably not being a great idea)?
--
I'm not using/familiar with MVVM at all (yet), and I'm not particularly concerned with optimal performance as this is a one off temporary project that will soon die and be re-worked. I've read ways how to access the data in a parent window from a child, but I didn't find scenarios that really matched up with this.
Once again, I'm still familiarizing myself with C#, WPF and general coding practices (it's been a couple years), so if using a ContentControl or UserControl here isn't optimal, (or mixing the two doesn't make sense) that information would be greatly appreciated; however, in this scenario, I'm more concerned with just getting this working until I can learn more proper techniques later.
Instead of trying to access the TextBox inside the UserControl, you can expose properties and methods on the UserControl itself to interact with what's inside. In your case, you could add a property that returns the current value of TextBox.Text. You can also add dependency properties to facilitate binding.
You can use the LogicalTreeHelper to search by name.
So for your example to access TextBox1
var txtBox = LogicalTreeHelper.FindLogicalNode(MainContentControl, "TextBox1") as TextBox;
Do_Something_Function(txtBox?.Text);
It definitely looks like the design should be improved, but just to get this working you can do the following:
private void Submit_Btn_Click(object sender, RoutedEventArgs e)
{
var controlAsUserControl1 = MainContentControl.Content as UserControl1;
if (controlAsUserControl1 != null)
{
Debug.WriteLine(controlAsUserControl1.TxtBox1.Text);
}
}

Named WPF control can't be found when added procedurally

We are using a third party editing control (TxTextControl) that has various toolbars and other controls which can be attached to it. This is done by placing the toolbars somewhere in the view and associating them by name. It looks something like this:
<tx:RulerBar x:Name="rulerBar"/>
<tx:TextControl RulerBar="rulerBar"/>
This works fine when defined in XAML, but we have a scenario where we need to build this procedurally. For some reason when we try associate the RulerBar (or any other toolbars/controls) it throws an error that it can't locate the toolbar. For example:
DockPanel dock = new DockPanel();
dock.Children.Add(new RulerBar { Name="rulerBar" });
dock.Children.Add(new TextControl { RulerBar = "rulerBar" });
I have also tried adding Loaded event handlers and deferred the RulerBar association until both controls were fully loaded but I still get the same error. Should this simply work, or is there some trick I'm missing? Unfortunately name association is the only mechanism they provide, and we can't associate the controls by reference.
In WPF, the XAML parsing process associates the value of an element's x:Name attribute with its name, enabling lookup by name.
So why does assigning the Name property in XAML work? This is since the Name property is marked with an attribute which instructs the parser to treat it like it treats an x:Name attribute.
Since you do not implement the same logic as the xaml parser does, you will have to do some additional coding in order for your code to work..
I myself did not know how to do that, but fortunately #Grx70 did.. see his answer.
#Eyal Perry is correct with his diagnose of the problem. In order to make it work you should use the FrameworkElement.RegisterName method. Here's an example of how to use it:
DockPanel dock = new DockPanel();
var ruler = new RulerBar { Name = "rulerBar" };
dock.RegisterName(ruler.Name, ruler);
dock.Children.Add(ruler);
dock.Children.Add(new TextControl { RulerBar = "rulerBar" });

wpf collectionviewsource in viewmodel or xaml code-behind

I am in this dilemma and hope someone can help me out
sorry I cannot paste code here as company block posting here.
i am trying to use collectionviewsource in xaml. i tried two ways, static resource and cvs.source. first one works pretty well but limitation is i can only find resource from code-behind. but control ui and disaplay ui not on same view, i don't know how to trigger sort/filter
so i move to second option, i put cvs in view model with properties exposed to both ui. but i got this famous error "trying to change ui not owned by this thread"
so generally what is good practice of where to put csv. i checked many places suggesting second option http://www.xamlplayground.org/post/2009/07/18/Use-CollectionViewSource-effectively-in-MVVM-applications.aspx and XAML Binding to a CollectionViewSource property on a ViewModel but seems no one mentioned ui thread ownership issue. am I doing something really stupid
thanks
If you keep having problems with threads, use a Dispatcher:
Application.Current.Dispatcher.Invoke(
new Action(() => /* modify the collection */));
Or use EnableCollectionSynchronization method, which is new in WPF 4.5 and will do the same for you:
private static object syncObject = new object();
//...
BindingOperations.EnableCollectionSynchronization(yourCollection, syncObject);
Read more about it here.

WPF ScatterView binding to multiple sources

I am using a ScatterView and am currently binding to a folder so that when my app starts up some sample images are displayed, this works great.
<s:ScatterView x:Name="MainScatterView">
<s:ScatterView.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}"/>
</DataTemplate>
</s:ScatterView.ItemTemplate>
</s:ScatterView>
I then set the binding using
scatter.ItemsSource =
System.IO.Directory.GetFiles(imagesPath, "*.jpg");
This works great but then when I try add further images:
Image img = new Image();
img.Source =
new BitmapImage(new Uri("\\Resources\\Koala.jpg", UriKind.Relative));
scatter.Items.Add(img);
I get an InvalidOperationException: Operation not valid when ItemSource is in use.
What is the best way to handle this. Remove the binding and add the images manually on startup? I'm assuming then since the ItemSource is the same any further additions wont cause any problems? Or is there a better way to handle this since the binding method works really well.
cheers
This calls for a ViewModel
This type of problem, binding working well for the simple case but starting to fall down as you add scenarios, is a great indicator that it's time to use Model - View - ViewModel.
Roughly speaking, the idea is that you have a View (your XAML) and a Model (your data, in this case a set of files). But instead of directly binding the View to the Data, you add an intermediate class called the ViewModel. Your View binds to the ViewModel and your ViewModel loads itself from the Model. This gives you wiggle room to do more than simple things when loading data to be bound.
What does that mean here? It would look like:
public class MainViewModel
{
// ObservableCollection adds databinding goodness so when you add a new file,
// the UI automatically refreshes
public ObservableCollection<string> Images { get; private set; }
public MainViewModel(string path)
{
Images = new ObservableCollection<string>();
Images.AddRange(Directory.GetFiles(path, "*.jpg"));
}
public void AddImage(string path)
{
Images.Add(path);
}
}
and now in your xaml, you set your datacontext to new MainViewModel. You can do this in code behind or using a StaticResource, if you use a StaticResource you need a ctor that takes no parameters so you'll have to set your initial directory in some other way. Your binding then looks like:
<Image Source={Binding Images} />
Take a good look at the M-V-VM pattern. You'll find that it makes databinding problems like this easier and also has a host of other benefits like fewer event handlers (so fewer reference leaks), better testability, easier to work with Blend, and easier to add new types of UI technologies.
I'm also new to Surface development, anyway what I have is remove the databinding and add the images manually via a for loop.

WPF - FindName Returns null when it should not

FindName is broken for me :(
The object I am looking for is there. I have proof.
Here is the scenario:
ToggleButton button = (ToggleButton)sender;
Popup popup = (Popup)button.FindName("popSelectIteration");
popup is null but not always. Just sometimes. But even when it is set to null the child I am looking for is there.
I put a break point in when it was null and grabbed these two screenshots.
The is where FindName is returning null for "popSelectIteration".
But if you dig into the watch, you see that the child is there.
So what am I missing? Why does FindName not find it? As you can see from the screen shot this is not a timing issue (the FindName watch is null but the direct path is fine).
Is there a better way to find a control?
Side Note: If you are intersted in the XAML for the toggle button in question it can be found in this question: WPF - FrameworkElement - Enumerate all decendents?.
Update: I did some digging to see why this fails some times and other times it works. I have an animation that calls NameScope.SetNameScope((DependencyObject)form, new NameScope()); (Full method code here). Right after that call the FindName starts to fail.
I don't really understand that call. I think I copied and pasted the code. Anyway, I commented it out. But I would love know why this is failing.
I would guess it has to do with the difference between the visual and logical tree. The control is in the logical tree but maybe the template for this control has not been applied yet and therefore FindName won't return anything useful.
You could try to call ApplyTemplate(); on the container first.
This would also explain why it returns something sometimes.
Try
LogicalTreeHelper.FindLogicalNode(button, "popSelectIteration");
Little late to the party (and not actually answer to OP question), but
when you add elements dynamically, they are not findable by FindName.
You need to register them by calling RegisterName.
Example:
string number = GenerateNumber();
Button myButton = new Button();
myButton.Content = number;
myButton.Name = "button_" + number;
RegisterName(myButton.Name, myButton);
Panel.Children.Add(myButton);
object o = Panel.FindName(myButton.Name);
Maybe someone might find this useful.
In my experience, this happens when you add items via code-behind. I've found that you can fool FindName() (or the animation framework) via name scopes. That is, when you create your control, you do
NameScope.GetNameScope(yourContainer).RegisterName("name of your control", yourControlInstance);
For this to work reliably, though, you must make sure that you unregister the name:
NameScope.GetNameScope(yourContainer).UnregisterName("name of your control");
Posting this for future reference.
I have meet the same question now, but I use the method like so:
#region Override - OnApplyTemplate
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.PART_ListViewLeft = GetTemplateChild(cPART_ListViewLeft) as ListView;
this.PART_ListViewCenter = GetTemplateChild(cPART_ListViewCenter) as ListView;
this.PART_ListViewRight = GetTemplateChild(cPART_ListViewRight) as ListView;
this.PART_GridViewLeft = GetTemplateChild(cPART_GridViewLeft) as DsxGridView;
this.PART_GridViewCenter = GetTemplateChild(cPART_GridViewCenter) as DsxGridView;
this.PART_GridViewRight = GetTemplateChild(cPART_GridViewRight) as DsxGridView;
if(this.PART_ListViewLeft!=null)
this.PART_ListViewLeft .AlternationCount = this.AlternatingRowBrushes.Count;
if(this.PART_ListViewCenter!=null)
this.PART_ListViewCenter .AlternationCount = this.AlternatingRowBrushes.Count;
if(this.PART_ListViewRight!=null)
this.PART_ListViewRight .AlternationCount = this.AlternatingRowBrushes.Count;
// ApplyTempleted = true;
CreateColumnLayout();
}
#endregion
If the Control is dynamic create and of which or whose container the 'Visibility' is set to hide or Collapsed, then the code this.PART_ListViewLeft = GetTemplateChild(cPART_ListViewLeft) as ListView; will always return null, the reason is that the datatemplete has not yet been applied before OnApplyTemplate being called.
I would suggest to avoid using FindName function, based on my experience, expecially problematic when you try to find something in the DataTemplate applied to some control.
Instead , if it possible (based on your software architecture) declare Popup in XAML and
refer to it like resource or use Binding to set some Model property to it's reference.
Good luck.
Try to use button.FindResource("popSelectIteration")
ellipseStoryboard.Children.Add(myRectAnimation);
containerCanvas.Children.Add(myPath);
After you add register the controls like
RegisterName("TextBlock1", Var_TextBox);
or
RegisterName(myRectAnimation.Name,myRectAnimation);
RegisterName(myPath.Name,myPath);

Categories

Resources