In my application I'm using Prism and MahApps.metro. I created a RegionAdapter for FlyoutsControl, this is working like a charm.
The only problem is, that when I first navigate the View into Flyout Region, the Flyout pops up instead of sliding in from the side.
I can imagine this is because it is created at runtime and added to the FlyoutsControl at runtime, but is there a possibility to create the Flyout, add it to the FlyoutsControl and then show it via Slide-In effect?
Just setting the IsOpen property to false and then to open doesn't work :(
I was trying to do the same thing and was having issues but later i figured out the solution.
First, define the region name FlyoutRegion on FlyoutsControl.
<mahApps:MetroWindow ...>
<mahApps:MetroWindow.Flyouts>
<mahApps:FlyoutsControl prism:RegionManager.RegionName="FlyoutRegion">
<mahApps:FlyoutsControl.ItemContainerStyle>
<Style TargetType="{x:Type mahApps:Flyout}">
<Setter Property="Header" Value="{Binding Header}" />
<Setter Property="IsOpen" Value="{Binding IsOpen}" />
<Setter Property="Position" Value="{Binding Position}" />
</Style>
</mahApps:FlyoutsControl.ItemContainerStyle>
</mahApps:FlyoutsControl>
</mahApps:MetroWindow.Flyouts>
</mahApps:MetroWindow>
Create the RegionAdapter and register it in the Bootstrapper.
[Export]
public class FlyoutsControlRegionAdapter : RegionAdapterBase<FlyoutsControl>
{
[ImportingConstructor]
public FlyoutsControlRegionAdapter(IRegionBehaviorFactory factory)
: base(factory)
{
}
protected override void Adapt(IRegion region, FlyoutsControl regionTarget)
{
region.ActiveViews.CollectionChanged += (s, e) =>
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (FrameworkElement element in e.NewItems)
{
Flyout flyout = new Flyout();
flyout.Content = element;
flyout.DataContext = element.DataContext;
regionTarget.Items.Add(flyout);
}
}
};
}
protected override IRegion CreateRegion()
{
return new AllActiveRegion();
}
}
Inside Bootstrapper
protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
{
var mappings = base.ConfigureRegionAdapterMappings();
mappings.RegisterMapping(typeof(FlyoutsControl), Container.GetExportedValue<FlyoutsControlRegionAdapter>());
return mappings;
}
Finally, register the desired View with FlyoutRegion.
regionManager.RegisterViewWithRegion("FlyoutRegion", typeof(FlyoutView));
The trick here is to expose the Header, IsOpen and Position properties in the ViewModel and associate it with FlyoutView.
You can refer the detail on this Code Project Link
Related
I need to show an image (fixed-size) in a WPF application.
It should be able to mark the image with pins as shown in above
image.
It should be able to add description for each pins and, when hovering
on the pin the description should be shown.
Finally I need to save all the information in SQL database to display
the pins again.
Is that possible to achieve this by creating a custom control?
Please suggest me your ideas for implementing this solution.
Providing examples will be highly appreciated.
To answer your question: Yes it's possible.
I would highly recommend the MVVM architectural pattern when working with WPF. What you need is:
A canvas control in order to use absolute positioning
An image control that will display the background image
A custom pin control that will display the image of the pins. This control could also contain a DataTemplate that will be used to generate the description control.
A custom control that will display information about the pin (Will be used in the popup)
An adorner that will render the pin info popup in an adorner layer. Place the adorner decorator in the same position as the canvas.
The information that you need to store about a pin:
Its Canvas.Top and Canvas.Left values
Properties that affect its visual characteristics (e.g. image, color etc)
The information displayed in its popup (e.g. description, image)
You can then read all the entries from the database and create a pin view model for each entry and bind the view models to an items control in the canvas. Don't forget to bind properties of the pin control to the respective values of its view model (e.g. Canvas.Left, Canvas.Top, Description etc).
As for the popup, once you created your adorner class, add an instance of it to the adorner layer of your canvas when you need to show the popup and remove it when you need to close the popup.
An example of the style of the map control can be seen below (Assumes view model of map control contains an observable collection of pins):
<Style TargetType="{x:Type local:Map}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Map}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<AdornerDecorator></AdornerDecorator>
<ItemsControl ItemsSource="{Binding Path=Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="White">
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:Pin></local:Pin>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Top" Value="{Binding Path=Y}" />
<Setter Property="Canvas.Left" Value="{Binding Path=X}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Here's an example of an adorner control that simply renders a given FrameworkElement:
public class ControlAdorner : Adorner {
FrameworkElement _control;
public FrameworkElement Control {
get {
return (_control);
}
set {
_control = value;
}
}
public ControlAdorner(UIElement Element, FrameworkElement Control)
: base(Element) {
this.Control = Control;
this.AddVisualChild(this.Control);
this.IsHitTestVisible = false;
}
protected override Visual GetVisualChild(int index) {
if (index != 0) throw new ArgumentOutOfRangeException();
return _control;
}
protected override int VisualChildrenCount {
get {
return 1;
}
}
public void UpdatePosition(Point point) {
VisualOffset = new Vector(point.X, point.Y);
this.InvalidateVisual();
}
protected override Size MeasureOverride(Size constraint) {
Control.Measure(constraint);
return Control.DesiredSize;
}
protected override Size ArrangeOverride(Size finalSize) {
Control.Arrange(new Rect(new Point(VisualOffset.X, VisualOffset.Y - 20), finalSize));
return new Size(Control.ActualWidth, Control.ActualHeight);
}
}
And here's how to make the Pin control display the adorner when the mouse is hovering:
public class Pin : Control {
public DataTemplate DescriptionItemTemplate {
get { return (DataTemplate)GetValue(DescriptionItemTemplateProperty); }
set { SetValue(DescriptionItemTemplateProperty, value); }
}
public static readonly DependencyProperty DescriptionItemTemplateProperty =
DependencyProperty.Register("DescriptionItemTemplate", typeof(DataTemplate), typeof(Pin), new PropertyMetadata(null));
ControlAdorner _adorner;
AdornerLayer _adornerLayer;
static Pin() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(Pin), new FrameworkPropertyMetadata(typeof(Pin)));
}
public Pin() {
this.MouseEnter += Pin_MouseEnter;
this.MouseLeave += Pin_MouseLeave;
}
private void Pin_MouseEnter(object sender, MouseEventArgs e) {
_adornerLayer = AdornerLayer.GetAdornerLayer(this);
FrameworkElement element = DescriptionItemTemplate.LoadContent() as FrameworkElement;
if (element == null) { return; }
element.DataContext = this.DataContext;
_adorner = new ControlAdorner(this, element);
_adornerLayer.Add(_adorner);
}
private void Pin_MouseLeave(object sender, MouseEventArgs e) {
_adornerLayer.Remove(_adorner);
_adorner = null;
}
}
I wrote custom TreeView control.
XAML:
<TreeView x:Class="EArchiveMaster.View.MyTreeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<EventSetter Event="LostFocus" Handler="EventSetter_OnHandler" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
.cs
public partial class MyTreeView
{
public event Action SomeItemLostFocus;
public MyTreeView()
{
InitializeComponent();
}
private void EventSetter_OnHandler(object sender, RoutedEventArgs e)
{
e.Handled = true;
if (SomeItemLostFocus != null)
SomeItemLostFocus();
}
}
But when I try to use it I got well known error:
Cannot set Name attribute value 'TextBox' on element 'TextBox'. 'TextBox' is under the scope of element 'MyTreeView', which already had a name registered when it was defined in another scope.
I found some receipts how to fix this error. Namely, specify .xaml part of control in its code-behind.
But I have no idea how can I do this.
The code clearly shows you want to extend TreeView. Basically if you want to build control that can hold some content(which can be named...), like ContentControl, ItemsControl, etc.. it is always better to go with CustomControl. UserControl with XAML and CS code is not suitable for this case.
In your case, create a class like below and extend the functionalities,
public class MyTreeView : TreeView
{
public event Action SomeItemLostFocus;
public MyTreeView()
{
DefaultStyleKey = typeof(MyTreeView);
}
public override void OnLostFocus(object sender, RoutedEventArgs e)
{
e.Handled = true;
if (SomeItemLostFocus != null)
SomeItemLostFocus();
}
}
If you want to customize the look and feel, you should override the default Style of the control. This style should be available in generic.xaml file inside Themes folder. More information on Custom Control development is here.
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded"
Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected"
Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
I found solution appropriate for me. This it the way how to define Style of TreeViewItem in code, not in XAML. Now I have TreeView definded only in code-behind, therefore, error will not be risen.
public class MyTreeView : TreeView
{
public event RoutedEventHandler ItemLostLogicFocus;
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
var itemContainerStyle = new Style
{
TargetType = typeof(TreeViewItem),
};
#region Binding
var expandedBinding = new Binding("IsExpanded")
{
Mode = BindingMode.TwoWay,
};
var selectedBinding = new Binding("IsSelected")
{
Mode = BindingMode.TwoWay,
};
#endregion
#region Setters
itemContainerStyle.Setters.Add(new Setter
{
Property = TreeViewItem.IsExpandedProperty,
Value = expandedBinding
});
itemContainerStyle.Setters.Add(new Setter
{
Property = TreeViewItem.IsSelectedProperty,
Value = selectedBinding
});
#endregion
#region EventSetters
itemContainerStyle.Setters.Add(new EventSetter
{
Event = LostFocusEvent,
Handler = new RoutedEventHandler(ItemLostLogicFocusHandler)
});
#endregion
ItemContainerStyle = itemContainerStyle;
}
private void ItemLostLogicFocusHandler(Object sender, RoutedEventArgs e)
{
e.Handled = true;
if (ItemLostLogicFocus != null)
ItemLostLogicFocus(sender, e);
}
}
I'm programming in WPF(c#). I'm trying to change value in a setter of style.
my style is:
<Style TargetType="Control" x:Key="st">
<Setter Property="FontFamily" Value="Tahoma"/>
<Setter Property="FontSize" Value="14"/>
</Style>
and I use it in a button:
<Button x:Name="btnCancel" Style="{StaticResource st}" Content="انصراف" Canvas.Left="30" Canvas.Top="18" Width="139" Height="53" FontFamily="2 badr" FlowDirection="LeftToRight" Click="btnCancel_Click_1" />
and what I try to do is this code:
Style style = new Style();
style = (Style) Resources["st"];
Setter setter =(Setter) style.Setters[1];
setter.Value = 30;
after setting font size to 30 I get this error?
After a “SetterCollectionBase” is in use (sealed), it cannot be modified
How can I solve this problem?
The styles can be set only once (sealed after compiling), you can't change it with code
so the solutions are
create a style by code
Style st = new Style(typeof(System.Windows.Controls.Control));
st.Setters.Add(new Setter(Control.FontFamilyProperty, new FontFamily("Tahoma")));
st.Setters.Add(new Setter(Control.FontSizeProperty, 14.0));
later you can change it
st.Setters.OfType<Setter>().FirstOrDefault(X => X.Property == Control.FontSizeProperty).Value = 30.0;//safer than Setters[1]
or
change the property directly
btnCancel.FontSize=30.0;
Since you are doing pure UI and behind the code while some answers recommend you to use MVVM which will really make a lot of things easier.
Why do you need to manipulate the Style? Is it just for the button and you want to manipulate its FontSize? I assume you are doing this on the Click event of the button where it changes the fontsize.
Try this then
private void btnCancel_Click_1(object sender, RoutedEventArgs e)
{
var button = sender as Button;
if (button != null) button.FontSize = 30;
}
You'll need to create a view model, something like this (I'm using the MVVM Lite class ViewModelBase, you just need something that supports property change notification):
public class MyViewModel : ViewModelBase
{
private double _FontSize = 0.0;
public double FontSize
{
get { return this._FontSize; }
set { this._FontSize = value; RaisePropertyChanged(() => this.FontSize); }
}
}
Then create an instance of it in your window along with a getter:
public partial class Window1 : Window
{
public MyViewModel MyViewModel {get; set;}
public Window1()
{
InitializeComponent();
this.MyViewModel = new MyViewModel { FontSize = 80 };
}
}
And then finally you need to bind your style to use the value in the view model:
<Window.Resources>
<Style TargetType="Control" x:Key="st">
<Setter Property="FontFamily" Value="Tahoma"/>
<Setter Property="FontSize" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Window}, Path=MyViewModel.FontSize}"/>
</Style>
</Window.Resources>
OK, a definite newbie here with WPF, and obviously need to keep learning more about MVVM, my code wasn't specifically designed that way, but I did designate one class to be the interface and controller for the GUI, whereas the model code resides in another set of classes. Have been scouring the web for examples, and questions similar to mine, of which there are plenty, but after three days of running through the maze I'm asking for help.
What I need is a simple dropdown menu, with items that can be dynamically updated (its an app that talks to a USB device, so however many are available should show up along with their device ID and serial number), and the currently selected item should show up on the Button (or whatever implementation of Dropdown menu I end up with). In this example, I just create a static list but that same list would be dynamically updated later on in the full app.
What I have so far looks like it is on the right track: I get the currently selected device id string to show up on the Button, and on pushing the Button, I get the list of all available devices (it doesn't bother me much that the currently selected device shows up redundantly in the list). However, I am not able to hook into any event when an item is selected, and thus can't update the item in the button, or do anything else for that matter.
My XAML below. Note that this was roughly hacked together, and there are some things in here that make no sense, like "IsActive" for the "IsChecked" property, that came from examples. The big problem is that as far as I can tell, none of the Setter properties in the ContextMenu.Resources seem to be doing anything at all...tried changing the fontsize to no avail. And the really big problem, of course, is that the "MyCommand" binding isn't working, that method never gets called.
<Label Content="Device Selected:" HorizontalAlignment="Left" Margin="25,22,0,0" VerticalAlignment="Top" Width="124" FontWeight="Bold" FontSize="14" Height="25"/>
<Button x:Name="DeviceSelMenuButton" Content="{Binding DeviceID_and_SN, Mode=TwoWay}" HorizontalAlignment="Left" Height="28" Margin="25,52,0,0" VerticalAlignment="Top" Width="187" FontSize="14" Click="DeviceSelMenuButton_Click">
<Button.ContextMenu>
<ContextMenu ItemsSource="{Binding DeviceID_SN_Collection, Mode=TwoWay}">
<ContextMenu.Resources>
<Style x:Key="SelectDeviceStyle" TargetType="MenuItem">
<Setter Property="Command" Value="{Binding MyCommand}"/>
<Setter Property="CommandTarget" Value="{Binding RelativeSource Self}"/>
<Setter Property="IsChecked" Value="{Binding IsActive}"/>
<Setter Property="IsCheckable" Value="True"/>
<Setter Property="FontSize" Value="14"/>
</Style>
</ContextMenu.Resources>
</ContextMenu>
</Button.ContextMenu>
</Button>
And the code from MainWindow.xaml.cs:
public partial class MainWindow : Window
{
CustomDeviceGUI _customDeviceGui = new CustomDeviceGUI();
public MainWindow()
{
InitializeComponent();
this.DataContext = _customDeviceGui;
}
private void DeviceSelMenuButton_Click(object sender, RoutedEventArgs e)
{
// " (sender as Button)" is PlacementTarget
(sender as Button).ContextMenu.IsEnabled = true;
(sender as Button).ContextMenu.PlacementTarget = (sender as Button);
(sender as Button).ContextMenu.Placement = System.Windows.Controls.Primitives.PlacementMode.Bottom;
(sender as Button).ContextMenu.IsOpen = true;
}
private void SomeMethod(object sender, DataTransferEventArgs e)
{
// TODO Somehow get the index of the selected menu item (collection index, 0-based)
// int selIndex = (sender as Button).ContextMenu.Items.IndexOf ??
_customDeviceGui.UpdateDeviceID("RelayPro id updated");
}
}
And the GUI code:
class CustomDeviceGUI : INotifyPropertyChanged
{
// Declare the event
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private string _deviceDisplayString;
private ICommand _updateMenu;
List<string> ControllerDeviceList = new List<string>();
private System.Collections.ObjectModel.ObservableCollection<string> _DeviceID_SN_Collection = new System.Collections.ObjectModel.ObservableCollection<string>();
// CTOR
public CustomDeviceGUI()
{
ControllerDeviceList.Add("CustomDevice Device 1");
ControllerDeviceList.Add("CustomDevice Device 2");
ControllerDeviceList.Add("CustomDevice Device 3");
ControllerDeviceList.Add("CustomDevice Device 6");
UpdateDeviceID(ControllerDeviceList[0]);
}
#region CustomDeviceGUI Properties
public System.Collections.ObjectModel.ObservableCollection<string> DeviceID_SN_Collection
{
get
{
_DeviceID_SN_Collection.Clear();
foreach (string str in ControllerDeviceList)
{
_DeviceID_SN_Collection.Add(str);
}
return _DeviceID_SN_Collection;
}
private set
{
_DeviceID_SN_Collection = value;
}
}
public string DeviceID_and_SN
{
get
{
return _deviceDisplayString;
}
private set
{
_deviceDisplayString = value;
}
}
public ICommand MyCommand
{
get
{
if (_updateMenu == null)
_updateMenu = new MyGuiCommand();
return _updateMenu;
}
}
#endregion
#region Public Methods
public void UpdateDeviceID(string deviceID)
{
this._deviceDisplayString = deviceID;
RaisePropertyChangeEvent("DeviceID_and_SN");
RaisePropertyChangeEvent("DeviceID_SN_Collection");
}
#endregion
protected void RaisePropertyChangeEvent(string name)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
try
{
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
catch (Exception e)
{
// ... TODO Remove this catchall or find specific exceptions
}
}
public class MyGuiCommand : ICommand
{
public void Execute(object parameter)
{
// Debug.WriteLine("Hello, world");
int hmm = 3;
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged // was ;
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
} // class CustomDeviceGUI
All the changes I had to make were in XAML. Primarily it was a matter of using the ancestor to get the right data context. I also switched to ContextMenu.ItemContainer instead of ContextMenu.Resources.
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Button}}, Path=DataContext.MyCommand}"/>
</Style>
</ContextMenu.ItemContainerStyle>
Eventough I'm not sure I think that the:
<Setter Property="Command" Value="{Binding MyCommand}"/>
binding needs a RoutedUICommand object.
EDIT:
Another thing that i have noticed is that you don't set any command bindings before. Like this:
<Window.CommandBindings>
<CommandBinding Command="MyCommand" Executed="Execute" />
</Window.CommandBindings>
just an example you can set CommandBindings to many others controls.
I have a Treeview which is doing lazy loading. I used MVVM. I wanted to select the top node of the tree by default when my application launches.
I think there is a better way... Just ceate a class that inherits from System.Windows.Controls.TreeView and override OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e). And in this method put this code:
if (base.SelectedItem == null)
{
if(base.Items.Count != 0)
{
(base.ItemContainerGenerator.ContainerFromItem(base.Items[0]) as TreeViewItem).IsSelected = true;
}
}
base.OnItemsChanged(e);
And that's it.
The easiest way to do this is use a style with an IsSelected property:
<Style x:Key="SelectableTreeViewItem" TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
Then expose this property in your model, or more specifically in the object that you bind to for your top level node.
public class MyTopLevelFoo
{
public bool IsSelected { get; set; }
}
...and set it to true when you initially load:
IsSelected = true;
Just use Loaded Event
private void tvComponents_Loaded(object sender, RoutedEventArgs e)
{
(tvComponents.ItemContainerGenerator.ContainerFromIndex(0) as TreeViewItem).IsSelected = true;
}