I spent several hours searching and testing and can't get it to work. I want to have a UserControl that exposes a template to fill a section of the UserControl. I got it to work by creating a DependencyProperty of type ContentTemplate (or DataTemplate?). Then I display it like this
<ContentControl x:Name="PlayerContent" ContentTemplate="{Binding PlayerTemplate, ElementName=W}" />
Now the problem is that when I use the UserControl I cannot set element names within the template.
<local:MediaPlayerWpf x:Name="PlayerUI" Height="auto" Width="auto">
<local:MediaPlayerWpf.PlayerTemplate>
<ControlTemplate>
<WindowsFormsHost x:Name="Host" Focusable="False" />
</ControlTemplate>
</local:MediaPlayerWpf.PlayerTemplate>
</local:MediaPlayerWpf>
This throws
Cannot set Name attribute value 'Host' on element
'WindowsFormsHost'. 'WindowsFormsHost' is under the
scope of element 'MediaPlayerWpf', which already had a name registered
when it was defined in another scope.
As a result, I have no way of accessing the control defined within the template. I also found no way of accessing the root of the children displayed in the ContentControl.
How can I access the "Host" control defined in the template?
It seems we can't use templates in such a way within UserControl. I don't know whether there's a work-around.
I ended up taking a different approach. I exposed a property to set the content.
public static DependencyProperty HostProperty = DependencyProperty.Register("Host", typeof(PlayerBase), typeof(MediaPlayerWpf), new PropertyMetadata(null, OnHostChanged));
public PlayerBase Host { get => (PlayerBase)base.GetValue(HostProperty); set => base.SetValue(HostProperty, value); }
private static void OnHostChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
MediaPlayerWpf P = d as MediaPlayerWpf;
if (e.OldValue != null)
P.HostGrid.Children.Remove(e.OldValue as PlayerBase);
if (e.NewValue != null) {
P.HostGrid.Children.Add(e.NewValue as PlayerBase);
P.UI.PlayerHost = e.NewValue as PlayerBase;
}
}
Then in the code-behind where the class is being used, I set it like this
Player.Host = new MpvMediaPlayerHost();
At least it's working.
EDIT: A better solution is to switch from User Control to Custom Control. I still applied the solution above, and switching to Custom Control allowed me to set the Host in a derived class' constructor.
Related
In our WPF software, we used a ControlTemplate which defines a ToggleButton that causes the window to shrink/extend. The definition of ToggleButton is given below:
<ToggleButton ToolTip="Standard/Extended" Grid.Column="0"
x:Name="PART_MaximizeToggle" VerticalAlignment="Top"
HorizontalAlignment="Right" Margin="0,5,5,0"
Width="14" Height="14" Cursor="Hand">
We're creating a custom DockPanel which contains this button at the upper right corner. Our application can contain up to three of this DockPanels at the same time:
The small rectangle on the right of each DockPanel is shown in the image above.
Notice from the definition that all three of the rectangles have same name: "PART_MaximizeToggle". This causes trouble when writing CodedUI programs to automate testing. CodedUI captures all of their FriendlyNames as "PART_MaximizeToggle" with Name field empty. The locations and sequence of the DockPanels can change based or what the user want.
How can we make CodedUI capture exact the button where a click is expected? I was thinking of making the Name of each toggle button dynamic but fixed for a specific DockPanel.
How can I do that? Is there a better approach?
You could assign (and register) the names automatically via an AttachedProperty that increments a counter for each prefix.
(This is just a proof of concept, you should also check that the names are valid)
public static class TestingProperties
{
private static readonly Dictionary<string, int> _counter = new Dictionary<string, int>();
public static readonly DependencyProperty AutoNameProperty = DependencyProperty.RegisterAttached(
"AutoName", typeof(string), typeof(TestingProperties), new PropertyMetadata(default(string), OnAutoNamePropertyChanged));
private static void OnAutoNamePropertyChanged(DependencyObject element, DependencyPropertyChangedEventArgs eventArgs)
{
string value = (string) eventArgs.NewValue;
if (String.IsNullOrWhiteSpace(value)) return;
if (DesignerProperties.GetIsInDesignMode(element)) return;
if (!(element is FrameworkElement)) return;
int index = 0;
if (!_counter.ContainsKey(value))
_counter.Add(value, index);
else
index = ++_counter[value];
string name = String.Format("{0}_{1}", value, index);
((FrameworkElement)element).Name = name;
((FrameworkElement)element).RegisterName(name, element);
}
public static void SetAutoName(DependencyObject element, string value)
{
element.SetValue(AutoNameProperty, value);
}
public static string GetAutoName(DependencyObject element)
{
return (string)element.GetValue(AutoNameProperty);
}
}
Usage in XAML:
<!-- will be Button_0 -->
<Button namespace:TestingProperties.AutoName="Button"/>
<!-- will be Button_1 -->
<Button namespace:TestingProperties.AutoName="Button"/>
<!-- will be Button_2 -->
<Button namespace:TestingProperties.AutoName="Button"/>
Resulting Visual-Tree:
Manfred Radlwimmer's solution is useful but makes the controls code behind harder.
Any dynamic code in the Controls' OnApplyTemplate that searches for that template part will become a pain.
An alternative would be to use same trick (generation of a unique id) for the automation id instead and use the automation id in the tests.
See:
http://www.jonathanantoine.com/2011/11/03/coded-ui-tests-automationid-or-how-to-find-the-chose-one-control/
Is it possible to make the whole text area of the RadComboBox clickable while having IsEditable=true and ReadOnly=True?
I would just set IsEditable = false but unfortunately I need it to be editable in order to display custom text when something is selected (I have it set so multiple things can be selected and present a list of the selected items). If I disable IsEditable then I lose the .Text attribute and can't set a custom text.
My two best bets would be:
1) somehow apply a style that makes the whole textbar clickable and not just the arrow
2) somehow apply custom text display when IsEditable is set to false.
Unfortunately I don't know how to do either so any help would be nice. Thanks
Edit: This would be ideal, except that we're using Silverlight and not ASP.net
http://demos.telerik.com/aspnet-ajax/combobox/examples/functionality/checkboxes/defaultcs.aspx
This is probably more realistic, just to somehow make the text area clickable so it opens the dropdown menu. Just like the ComboBox on the right, minus being able to type. http://demos.telerik.com/aspnet-ajax/combobox/examples/functionality/comboboxvsdropdownlist/defaultcs.aspx
I can think of several solutions, of varying elegance. Here is one that might be suitable to close your remaining gap between the Arrow-ToggleButton and the Text-Input-Area. And now that I think about it... maybe you can get rid of that rather smelly and fragile side-effect-piggybacking with the OpenDropDownOnFocus property (which will break as soon as a click does not change the focus owner).
Register a MouseLeftButtonDown click handler with the RadComboBox, you can choose to get all events, not only unhandled events. Then we can toggle the DropDown from there. But we don't want to interfere with the Arrow-ToggleButton, therefore we check from where the mouse click originated.
public class MyView : UserControl
{
public MyView()
{
InitializeComponent();
MouseButtonEventHandler handler = OnComboBoxClicked;
radComboBox.AddHandler( UIElement.MouseLeftButtonDownEvent, handler,
handledEventsToo: true );
}
private void OnComboBoxClicked( object sender, MouseButtonEventArgs args )
{
if (!args.Handled ||
!args.IsRoutedEventFromToggleButton(
togglebuttonAncestorToStopTheSearch: (UIElement) sender))
{
ToggleDropDown();
}
}
}
and extension methods for easier use:
public static class ControlExtensions
{
public static bool IsRoutedEventFromToggleButton(
this RoutedEventArgs args,
UIElement togglebuttonAncestorToStopTheSearch )
{
ToggleButton toggleButton = ((UIElement) args.OriginalSource)
.GetAncestor<ToggleButton>( togglebuttonAncestorToStopTheSearch );
return toggleButton != null;
}
public static TAncestor GetAncestor<TAncestor>(
this DependencyObject subElement,
UIElement potentialAncestorToStopTheSearch )
where TAncestor : DependencyObject
{
DependencyObject parent;
for (DependencyObject subControl = subElement; subControl != null;
subControl = parent)
{
if (subControl is TAncestor) return (TAncestor) subControl;
if (object.ReferenceEquals( subControl,
potentialAncestorToStopTheSearch )) return null;
parent = VisualTreeHelper.GetParent( subControl );
if (parent == null)
{
FrameworkElement element = subControl as FrameworkElement;
if (element != null)
{
parent = element.Parent;
}
}
}
return null;
}
}
I ended up finding a multiselectcombobox that someone else implemented here:
http://www.telerik.com/support/code-library/a-multiselect-combobox
I didn't need the whole combobox itself since we already had one implemented so I just looked at how the person was displaying a custom message while the combo box IsEditable was set to false.
After looking at that code for a while and seeing how I can make it work for me, I put
<ucControls:RadComboBox.SelectionBoxTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text,ElementName=RadCombo}" />
</DataTemplate>
</ucControls:RadComboBox.SelectionBoxTemplate>
inside the XAML of our own custom MultiSelectComboBox. (RadCombo being the name of the particular control that I wanted the Text to be linked to)
<ucControls:RadComboBox
x:Name="RadCombo"
Text=""
........
<ucControls:RadComboBox.SelectionBoxTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text,ElementName=RadCombo}" />
</DataTemplate>
</ucControls:RadComboBox.SelectionBoxTemplate>
.......
</ucControls:RadComboBox>
Using the built in SelectionBoxTemplate, this basically just added a TextBlock overlay, and the content was bound to the RadComboBox's own Text, so when we would set the Text of the RadComboBox, the TextBlock would update itself.
This was the most effective way for us to do it because it required minimal code changes, and no structure changes since we already had all the code in place for checking boxes and setting a custom text.
Hope this helps someone, best of luck!
In my program's main window I have a TreeView and a ContentPresenter. The display of the ContentPresenter is determined by what node is selected in the TreeView.
The name of one of my nodes is allowed to be changed by the user via contentMenu. All the user has to do is right click the node and select the new name out of the choices. The ContentPresenter is supposed to have a null display until the user chooses a name for the node.
The problem occurs when a new name is selected from the contentMenu. The ContentPresenter's display changes, like it should, but only after the user selects a different node (changing the display), and then re-selects the original node.
How do I make it so that the display on the ContentPresenter changes right when the TreeView node's name is changed?
TreeViewViewModel:
public class TreeViewViewModel : PropertyChangedBase
{
public TreeViewViewModel()
{
Node = new Node() { NodeName = "Blank", NodeDataModel = new NodeModel(),
Commands = { new Command(nodeType_name1), new Command(nodeType_name2) } };
}
//These functions call to the NodeName property in the TreeView's Data Model
private void nodeType_name1()
{
Node.NodeName = "Name1";
}
private void nodeType_name2()
{
Node.NodeName = "Name2";
}
}
XAML for MainWindow:
<!-- Tree view items & Functions -->
<TreeView Name="Tree_One" ItemsSource="{Binding DataTree.Data}" ... >
<TreeView.Resources>
<SolidColorBrush Color="LightSkyBlue" x:Key="{x:Static SystemColors.HighlightBrushKey}" />
</TreeView.Resources>
</TreeView>
<!--- Left Widget -->
<ContentPresenter Content="{Binding LeftWidget}" />
MainWindowViewModel:
public class MainWindowViewModel : PropertyChangedBase
{
private TreeViewViewModel _dataTree;
public MainWindowViewModel()
{
_dataTree = new TreeViewViewModel();
}
public TreeViewViewModel DataTree { ... }
//This function is in charge of changing the display of the ContentPresenter
// I think that my problem can probably be solved by doing something here
public void ChangeViews()
{
if (_dataTree.SelectedItem is Node)
{
var _node = _dataTree.SelectedItem as Node;
var nodeViewModel = new NodeViewModel(_node.NodeDataModel);
if (_node.NodeName== "Unknown")
LeftWidget = null; //This is the Content Presenter **
if (_node.NodeName == "Name1")
{
LeftWidget = nodeViewModel;
}
if (_node.NodeName == "Name2") {...}
}
}
}
Duh, thats a alot of code and its pretty difficult to understand what you up to since you seem to have controls in your ViewModel.
Or at least it looks to me that you have them in ViewModel. That is not very MVVM-alike my friend. :)
"The problem occurs when a new name is selected from the contentMenu. The ContentPresenter's display changes, like it should, but only after the user selects a different node (changing the display), and then re-selects the original node."
The property changed is not being fired because the new selected value is equal to the old one.
Pretty obvious, right?... no property was actually changed
But why do you want the ContentPresenter to update itself with the value that it already has?
You said when you select a node the ContentPresenter displays it properly and when you re-select the same the ContentPresenter is not doing anything.
Its not doing anything because it think it doesnt need to. Which is true.
So the question is why would you make ContentPresenter force to refresh on each value no matter if old value is the same as new one?
Though if you want to hack/trick a little bit, you can always set ContentPresenter's Content to null before you assign another value. :)
However, post us more code and we will be able to provide you a better solution to your issue.
I was able to fix this issue by calling ChangeViews(); in my MainWindowViewModel from my TreeViewViewModel. I did this by using a delegate property in the TVVM, and adding it to my MWVM. By doing this, the display is updated whenever ChangeViews(); is called.
This is the answer that I used.
So i have several subcontrols that need to take the value of the parent controls dependency property. Is binding the value to the dependency property of the parent going to be faster than just creating a callback method to occur when the parent's dependency property changes?
I was about to code it such that:
ItemsControl ic = this.signal_viewer_item_control;
int count = VisualTreeHelper.GetChildrenCount(ic);
foreach (var item in ic.Items)
{
ContentPresenter container = ic.ItemContainerGenerator.ContainerFromItem(item) as ContentPresenter;
if (container != null)
{
SignalGraph sg = container.ContentTemplate.FindName("signal_graph", container) as SignalGraph;
if (sg != null)
{
sg.GraphPenWidth = GraphPenWidth;
sg.DrawSignals();
}
}
}
so that I just manually modify the subcontrols graphpenwidth. Is it better to make that a dependency property and bind it to the parent's graphpenwidth value? i just thought that creating two dependency properties would be unnecessary overhead, but i'm wondering if there are benefits to having it in this situation
Edit: so i went back and tried to use dependency properties to compare the two, but then I can't seem to get it to work.
<wpfExp:SignalGraph
x:Name="signal_graph"
Height="75"
Signal="{Binding}"
signal_graph_window_width="{Binding ElementName=signal_box, Path=signal_graph_window_width, Mode=OneWay}"
X_Scale="{Binding ElementName=signal_box, Path=X_Scale, Mode=OneWay}"
MaxTimeValue="{Binding Source = {StaticResource ResourceKey=signal_data}, Path = MaxTimeValue, Mode=OneWay}">
<wpfExp:SignalGraph.GraphPenWidth>
<Binding ElementName="signal_box" Path="GraphPenWidth" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" NotifyOnTargetUpdated="True">
</Binding>
</wpfExp:SignalGraph.GraphPenWidth>
</wpfExp:SignalGraph>
I put a two way binding on graph penwidth between the two properties and then registered the new properties like so:
private static readonly DependencyProperty GraphPenWidthProperty =
DependencyProperty.Register("GraphPenWidth",
typeof(int), typeof(SignalGraph),
new FrameworkPropertyMetadata(new int(), new PropertyChangedCallback(GraphPenWidthChanged)));
public int GraphPenWidth
{
get
{
return (int)GetValue(GraphPenWidthProperty);
}
set
{
SetValue(GraphPenWidthProperty, value);
default_pen = new Pen(Brushes.Green, value);
}
}
private static void GraphPenWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SignalGraph sg = d as SignalGraph;
sg.DrawSignals();
}
but on modification the graphpenwidthchanged callback method is never being called. I'm wondering does it have something to do with it being in an itemtemplate? any clue what could cause it not to update?
If you dont have tons of bindings to different dependency properties, difference in performance between binding and manual setter is not noticeable. Anyway, if you are going to use manual setter, searching element in template every time your property is being changed - is not a good idea. What about returning a custom control in the GetContainerForItemOverride and storing its template child (SignalGraph in your case) as a property (you can get it in the OnApplyTemplate using the GetTemplateChild method)?
I wish to dynamically change the scroll position of a Silverlight ListBox from C#, and I need to know how to access the ScrollViewer element of a ListBox control from C#?
Thanks guys,
Jeff
From within a class that inherits from the ListBox class, you can use the Protected GetTemplateChild():
var myScrollviewer = myListBox.GetTemplateChild("ScrollViewer") as ScrollViewer;
If you want to access this from outside the ListBox, then exposing the ScrollViewer via a Property should work, again through inheritance.
CAVEAT: If you have set your own custom template, then this Scrollviewer may not exist. You can use the templates Scrollviewer name instead of the "ScrollViewer" in the method above.
Good question. I didn't find a way to do it directly, but came fairly close by looking at the Silverlight Controls project (they use the scrollviewer on the items control in some of the classes). Here is how you can get it, but it requires a custom listbox:
public class TestBox : ListBox
{
private ScrollViewer _scrollHost;
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
var itemsHost = VisualTreeHelper.GetParent(element) as Panel;
for (DependencyObject obj = itemsHost; obj != item && obj != null; obj = VisualTreeHelper.GetParent(obj))
{
ScrollViewer viewer = obj as ScrollViewer;
if (viewer != null)
{
_scrollHost = viewer;
break;
}
}
base.PrepareContainerForItemOverride(element, item);
}
}
There might be another way to hook into that event (or another way to get that panel), If you look at the template for the ListBox you will see the scroll viewer is actually named "ScrollViewer", however the GetTemplateChild method is protected so you would still need to create a custom class.
Let's make it easy...
In your Listbox template, you might find the ScrollViewer Control.
Add a Loaded Method for it, and you will get itself frome the sender arg.
private void ScrollViewer_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
myScrollViewer = (sender as ScrollViewer);
}
this works for me
You can call :
myListBox.ApplyTemplate();
to force the ListBox visual tree to be created, otherwise GetTemplateChild() will return Null if you attempt to access it immediatly.
This works well combined with "Erno de Weerd" explanation : inherit ListBox to be able to call GetTemplateChild() method.
I also tried :
to use ListBox extension method "GetScrollHost()" but it never worked for me (even after full page initialisations).
"FindName()", but it didn't work, even when i specified the ScrollViewer name into the ListBox Template.
Emmanuel (Silverlight 3)
ScrollViewer scrollViewer = yourListBox.getScrollHost();
Is null if no datasourse set to the listbox, in my case it return properly UI Element only after below code executed
myListBox.ItemsSource = list;