I have an application build with the MVVM pattern from Josh Smith (http://msdn.microsoft.com/en-us/magazine/dd419663.aspx).
When I have several workspaces opened in my app, I want to catch the event of switching workspaces/tabs so I can save the content of the current workspace first. I have looked throught WorkspaceViewModel and ViewModelBase, but I don't know how to add that EventHandler.
I have found a solution in another post, I just had to tweak it a little bit : What is the proper way to handle multiple datagrids in a tab control so that cells leave edit mode when the tabs are changed?
Basically I have added an EventHandler on PreviewMouseDown of my TabControl generating the different Workspaces.
private void TabControl_PreviewMouseDown(object sender, MouseButtonEventArgs e) {
MainWindow_VM dc = (MainWindow_VM)this.DataContext;
if (IsUnderTabHeader(e.OriginalSource as DependencyObject))
//Do what need to be done before switching workspace
// in my case, switch the focus to a dummy control so the objectContext would save everything, even the currently focused textbox
}
private bool IsUnderTabHeader(DependencyObject control)
{
if (control is TabItem)
{
return true;
}
DependencyObject parent = VisualTreeHelper.GetParent(control);
if (parent == null)
return false;
return IsUnderTabHeader(parent);
}
You should be able to bind the "Current" item of the tab to a variable in the model. When this changes, do your work.
Related
I am using the TreeView from the WinrtXamlToolkit. The default behavior of this control is to expand the nested items on double click of the header. The code responsible for this is here (TreeViewItem.cs line 1205).
private void OnHeaderMouseLeftButtonDown(object sender, PointerRoutedEventArgs e)
{
if (Interaction.AllowMouseLeftButtonDown(e))
{
// If the event hasn't already been handled and this item is
// focusable, then focus (and possibly expand if it was double
// clicked)
if (!e.Handled && IsEnabled)
{
if (Focus(FocusState.Programmatic))
{
e.Handled = true;
}
// Expand the item when double clicked
if (Interaction.ClickCount % 2 == 0)
{
bool opened = !IsExpanded;
UserInitiatedExpansion |= opened;
IsExpanded = opened;
e.Handled = true;
}
}
Interaction.OnMouseLeftButtonDownBase();
OnPointerPressed(e);
}
}
Is there a way to change this behavior to expand the items on single click or tap without actually copying the control and all it's related classes to my project?
It seems like an overkill to do this just to change a few lines of code.
I tried to do drag'n'drop stuff with that TreeView and was in a similar situation. My first move was to actually copy all the TreeView and its related classes and man there are a lot. There's a lot of internal stuff happening and I pretty much gave up interfering with it after a bunch of other stuff stopped working.
So my solution was to just have a specific control inside the ItemTemplate that handled dragging for me. For you this would be a Button whose Click you handle. In the eventhandler you will navigate up the visual tree to your TreeViewItem and change the IsExpanded.
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!
I have a WPF app with a Window and different UserControls are shown in it one by one no button clicks.
I import data from a file and all data is stored in a common object "ImportExportData". All UserControls are bind to respective Property (as custom objects like Data1, Data2...) of ImportExportData class.
In my USerControl I have combobox's for NumberZones proeprty those SelectionChanged event is handled respectively. In the SelectionChanged event of this combobox, based on the number selected that many rows are added to an ObservableCollection of Data2 property.
While importing data and setting the imported object (Data2) as the DataContext of USerControl2, it sets the NumberZones property value to the respective combobox and SelectionChanged event is fired as it should. At this time, the object already contains reqd rows in ObservableCollection and this event should not add it.
PArent window has a flag "importedData" that tells me that the object is imported. But I can't make that false once UserContrl2 is loaded, as their are their UC that will follow UC2. In UC2 I can create another flag "importing" and make it false once all UI is loaded. Thru which UC event can I know that UI is loaded and thus make "importing" as false ??
I am wondering how do I avoid from firing the SelectionChanged event when the imported object is populating the UI components. Which event of the UserControl will help me in this case maybe to keep a flag in USerControl2.
Any idea, suggestions please.
It is very hard to understand all of your question, so bear with me... I'll address each point that I understand.
Thru which UC event can I know that UI is loaded and thus make "importing" as false ??
Take a look at the FrameworkElement.Loaded Event page at MSDN.
I am wondering how do I avoid from firing the SelectionChanged event when the imported object is populating the UI components.
There are two way of achieving this goal... The first way does not stop the event from firing, but instead ignores it when data is being imported. basically involves temporarily unsubscribing from the SelectionChanged event and then re-subscribing to it. If I understand you correctly, you have a bool property in your parent Window and SelectionChanged handlers in your UserControls... first, you can add a bool property to each of your UserControls:
public bool CanChangeSelection { get; set; }
Now, in your parent Window (assuming that you have references to your controls) you can update your property:
private bool isImporting = false;
public bool IsImporting
{
get { return isImporting; }
set
{
isImporting = value;
UserControl1.CanChangeSelection = isImporting;
UserControl2.CanChangeSelection = isImporting;
...
UserControlN.CanChangeSelection = isImporting;
}
}
Then finally, in your control SelectionChanged handlers:
private void SelectionChangedHandler(object sender, RoutedEventArgs e)
{
if (CanChangeSelection)
{
// do your stuff in here
}
}
The second way basically involves temporarily unsubscribing from the SelectionChanged event and then re-subscribing to it. For this option, we need to change the definition of our new bool property in each of your UserControls:
private bool canChangeSelection = false;
public bool CanChangeSelection
{
get { return canChangeSelection; }
set
{
canChangeSelection = value;
if (!canChangeSelection)
{
if (SelectionChangedHandler != null) ComboBox1.SelectionChanged -=
SelectionChangedHandler;
}
else if (SelectionChangedHandler == null) ComboBox1.SelectionChanged +=
SelectionChangedHandler;
}
}
I personally prefer the first method as it is more straightforward.
Currently in my program in about 10 control event handlers I have this code:
if (!mapLoaded)
return;
When I load a map through the open file dialog I set mapLoaded to true. Another way to do this would be to just disable all the controls for startup and after loading a map to enable all the controls. Unfortunately there are 30+ controls and this is just 30 lines of..
a.Enabled = true;
b.Enabled = true;
c.Enabled = true;
I can't really do a foreach loop through this.Controls either because some of the controls are menustrip items, toolstrip items, panel items, scrollbars, splitters, et cetera and that loop doesn't cover that.
Ideally there would be a way to set every control's enabled property to true in a single and simple loop but I'm not sure of how to do that. Any ideas SO?
Use data binding:
Change mapLoaded into a property that notifies observers when its value has changed...
public bool MapLoaded
{
get
{
return mapLoaded;
}
set
{
if (value != mapLoaded)
{
mapLoaded = value;
MapLoadedChanged(this, EventArgs.Empty);
}
}
}
private bool mapLoaded;
public event EventHandler MapLoadedChanged = delegate {};
// ^ or implement INotifyPropertyChanged instead
Data-bind your controls' Enabled property to MapLoaded. You can set up the data bindings either using the Windows Forms designer, or using code, e.g. right after InitializeComponent();:
a.DataBindings.Add("Enabled", this, "MapLoaded");
b.DataBindings.Add("Enabled", this, "MapLoaded");
c.DataBindings.Add("Enabled", this, "MapLoaded");
How about changing your opening strategy, have a new form that let's your user load a map, and the simply not load your main form until one has been loaded?
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;