WPF, MVVM, and Menu Foreground Color - wpf

I'm new to both WPF and MVVM. I have searched for a good way to dynamically create menus in the MVVM parttern and I am not finding anything to my liking, so I rolled my own solution. It works, but for some reason the Foreground (text) color of the menus are sometimes (just sometimes) not correct.
I added a link for the image below.
http://img220.imageshack.us/img220/1912/badmenu.jpg (Dead Link)
My lowest submenu displays correctly with a white foreground, but its parent menus forground turned to black and is almost impossible to read. If I had hard coded the menus then the parent's forground color would be white. If I move my mouse over the parent its text will switch back to white and the submenu will become black.
Further, once I move my mouse away from the parent, all of its boolean properties IsHighlighted, IsSubmenuOpen, etc... become false, which surprising to me because I would think they should stay true. The end result is I haven't been able to solve this with a style trigger.
Here is my XAML .
<Window.Resources>
<DataTemplate DataType="{x:Type src:ParentMenu}" >
<Menu >
<MenuItem Header="{Binding MenuName}" ItemsSource="{Binding ChildMenuItems}" />
</Menu>
</DataTemplate>
<HierarchicalDataTemplate DataType="{x:Type src:ChildMenu}"
ItemsSource="{Binding ChildMenuItems}" >
<MenuItem Header="{Binding MenuName}" Command="{Binding Path=Command}" />
</HierarchicalDataTemplate>
' StackOverflow is masking my end tag for Window.Resources
<DockPanel>
<Menu DockPanel.Dock="Top" ItemsSource="{Binding Menus}" />
<Grid>
<!-- Add additional content here -->
</Grid>
</DockPanel>
Both ParentMenu and ChildMenu inherit from a common class that actually holds all the menus and exposes the sub-menus through the ChildMenuItems collection. ChildMenuItems is a list of ChildMenu objects. My ViewModels expose a list of ParentMenu objects.
There are probably better ways to accomplish what I want here. Here is an example:
img132.imageshack.us/img132/4160/bettermenu.jpg (Dead Link)
Any suggestions on what I'm doing wrong and/or how to fix the display problem?

The problem is that your VMs automatically get wrapped in MenuItems, so you essentially have MenuItems nested as the Header of MenuItems.
You can get around this by defining a Style (and pointing to it via ItemContainerStyle) that DataBinds to your VMs (Name to Header, DelegateCommands to Command, etc.) using MenuItem as the DataType.
An example of a way you can do this is below. Note that I've dropped the HierarchicalDataTemplate in favor of an ItemContainerStyle. I also took the liberty of defining a DataTemplate for your MainViewModel as it wasn't very clear how that was data bound.
<Window.Resources>
<DataTemplate DataType="{x:Type src:MainViewModel}">
<ItemsControl ItemsSource="{Binding Menus}"></ItemsControl>
</DataTemplate>
<DataTemplate DataType="{x:Type src:ParentMenu}" >
<Menu>
<MenuItem Header="{Binding Name}"
ItemsSource="{Binding ChildMenuItems}" ItemContainerStyle="{DynamicResource ChildMenuItemStyle}" />
</Menu>
</DataTemplate>
<Style x:Key="ChildMenuItemStyle" TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Name}"></Setter>
<Setter Property="ItemsSource" Value="{Binding ChildMenuItems}"></Setter>
</Style>
</Window.Resources>
I've also cut some of the Command binding out for simplicity, but you can add it back in as necessary.

As requested, here are my ViewModels.
ViewModelBase is the standard one created by studio. MainVieModel has got just enough to in it to create the test menus I was using to experiment with.
Basically I am working towards creating a Parent/Child menu classes I can use with a series of apps we sell to a broad collection of clients. I hope to make it where each customer can have a customizable collection of menus based upon their needs and which moudles they've purchased licenses for.
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class MainViewModel : ViewModelBase
{
public MainViewModel() { MakeMenus(); }
private void MakeMenus()
{
// Makes some dummy menus to test with.
Menus = new ObservableCollection<MyMenuItem>();
ParentMenu parent;
ChildMenu child;
parent = new ParentMenu("First Level");
Menus.Add(parent);
child = new ChildMenu(parent, "second level");
parent.ChildMenuItems.Add(child);
ChildMenu child2 = new ChildMenu(child, "third level");
child2.MenuCommand = new DelegateCommand(CommandTest,
CommandCanExecute_First);
child.ChildMenuItems.Add(child2);
child = new ChildMenu(parent, "second level 2");
parent.ChildMenuItems.Add(child);
child2 = new ChildMenu(child, "third level 2");
child2.MenuCommand = new DelegateCommand(CommandTest,
CommandCanExecute_Second);
child.ChildMenuItems.Add(child2);
parent = new ParentMenu("Another First");
parent.ChildMenuItems.Add(new ChildMenu(parent, "Another Second"));
Menus.Add(parent);
OnPropertyChanged("Menus");
}
private bool ExecuteToggle { get; set; }
private void CommandTest() { ExecuteToggle = !ExecuteToggle; }
public ObservableCollection<MyMenuItem> Menus { get; private set;}
public bool CommandCanExecute_First() { return ExecuteToggle; }
public bool CommandCanExecute_Second() { return !ExecuteToggle; }
}

Related

AvalonDock document view content disapear after float window

I'm using AvalonDock control in my project. When i move my document anywhere and detach from control, document content disapear. And if i redock document to a control, document content come out. I'm sure i'm missing something so simple but i don't understand the problem. Here is the code snippet from the MainView;
<xcad:DockingManager AllowMixedOrientation="True" DocumentsSource="{Binding DocumentViewModels}">
<xcad:DockingManager.Resources>
<DataTemplate DataType="{x:Type viewModels:WatchListViewModel}">
<local:WatchListView DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:ScanListViewModel}">
<local:ScanListView DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:SignalListViewModel}">
<local:SignalListView DataContext="{Binding}"/>
</DataTemplate>
</xcad:DockingManager.Resources>
<xcad:DockingManager.LayoutItemContainerStyle>
<Style TargetType="{x:Type xcad:LayoutItem}">
<Setter Property="Title" Value="{Binding Model.Document.Title}"/>
</Style>
</xcad:DockingManager.LayoutItemContainerStyle>
</xcad:DockingManager>
For clarify the problem want to share three screenshots. First screenshot is showing document before move anywhere. Second screenshot is showing document after move anywhere (floating). And third screenshot is showing re-dock to same place. Actually first and third image same but i want to show clearly that actually content still there.
You need to add a DataTemplateSelector to your code, in order to teach AvalonDock which DataTemplate used for your own View/ViewModel.
In order to do so, you need to define a new class like the following:
class PanesTemplateSelector : System.Windows.Controls.DataTemplateSelector
{
public DataTemplate WatchListViewTemplate { get; set; }
public DataTemplate ScanListViewTemplate { get; set; }
public DataTemplate SignalListViewTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is WatchListViewModel)
return WatchListViewTemplate;
if (item is ScanListViewModel)
return ScanListViewTemplate;
if (item is SignalListViewModel)
return SignalListViewTemplate;
return base.SelectTemplate(item, container);
}
}
Then you need to add this class yo your XAML as follow:
<xcad:DockingManager.LayoutItemTemplateSelector>
<s:PanesTemplateSelector>
<s:PanesTemplateSelector.WatchListViewTemplate>
<DataTemplate>
<p:WatchListView />
</DataTemplate>
</s:PanesTemplateSelector.WatchListViewTemplate>
<s:PanesTemplateSelector.ScanListViewTemplate>
<DataTemplate>
<p:ScanListView />
</DataTemplate>
</s:PanesTemplateSelector.ScanListViewTemplate>
<s:PanesTemplateSelector.SignalListViewTemplate>
<DataTemplate>
<p:SignalListView />
</DataTemplate>
</s:PanesTemplateSelector.SignalListViewTemplate>
</s:PanesTemplateSelector>
</xcad:DockingManager.LayoutItemTemplateSelector>
Where s links to the namespace where you define the PanesTemplateSelector and p links to the namespace where you define your own Views

WPF - How to implement DataTemplate with complex logic?

I am currently transferring my app from WinForms to WPF.
Since I'm new in WPF, I stucked at creating DataTemplates for my treeView items. The screenshot shows how my treeview looked in WinForms version, and I need to get close result in WPF.
(My WinForms treeview)
As you can see, my DataTemplate's logic should take into account these factors:
Node type / defines which icon and fields combination will be displayed for particular item (node). App has about 7-8 node types. Type stored in separate node's field.
Variable values / I need to replace with text if null, etc
Numeric variable values / e.g.: set gray color if zero, etc.
Other properties / e.g.: adding textblocks depending on boolean fields.
And so on...
All these factors result into huge amount of possible item params combinations.
Also I'm using DevComponents WPF DotNetBar AdvTree to divide item properties into columns. I presume I should create 'sub templates' for different field sets and compose from them the entire DataTemplate for each column.
I've learned about triggers, and have to say that implementing my logic with triggers will make my subtemplates huge anyway.
(Current state of my WPF treeview)
So here are my questions:
Are there any ways to dynamically compose complex templates with C# code (without creating raw XAML and loading it at runtime)?
Maybe I should use completely different way (instead of using DataTemplate)? In Winforms I just used OwnerDraw mode, so the task was MUCH easier than in WPF :(
And how to display nested properties inside template? e.g.: Item.Prop.Subprop1.Subprop2.Targetprop.
PS: English is not my first language, sorry for your eyes.
1) The answer is yes.
For exemple if you want to define a template in your window for a simple string
public MainWindow()
{
InitializeComponent();
DataTemplate template = new DataTemplate(typeof(string));
FrameworkElementFactory borderFactory = new FrameworkElementFactory(typeof(Border));
borderFactory.SetValue(Border.PaddingProperty, new Thickness(1));
borderFactory.SetValue(Border.BorderThicknessProperty, new Thickness(1));
borderFactory.SetValue(Border.BorderBrushProperty, Brushes.Red);
FrameworkElementFactory textFactory = new FrameworkElementFactory(typeof(TextBlock));
textFactory.SetBinding(TextBlock.TextProperty, new Binding
{
Mode = BindingMode.OneWay
});
borderFactory.AppendChild(textFactory);
template.VisualTree = borderFactory;
myControl.ContentTemplate = template;
}
And in the Window you just put something like
<ContentControl x:Name="myControl" Content="Test text" Margin="10"/>
Your content control will render the string surrounded by a red border.
But as you anc see it is really complex to define your templates in this way.
The only scenario where i could imagine this approache is for some kind of procedurally generated templates.
Another way is to generate a string for the template and then load it with XamlReader:
string xaml = "<Ellipse Name=\"EllipseAdded\" Width=\"300.5\" Height=\"200\"
Fill=\"Red\" xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"/>";
object ellipse = XamlReader.Load(xaml);
2) I don't really see the need to generate templates in code behind. For exemple for this kind of data structure:
public class User
{
public string Name { get; set; }
public User Friend { get; set; }
}
public class RootNode
{
public string Title { get; set; }
public User User { get; set; }
public List<Node> Nodes { get; set; }
}
public class Node
{
public string Title { get; set; }
public List<SubNode> SubNodes { get; set; }
}
public class SubNode
{
public string Title { get; set; }
}
You can define this type of template:
<Window
...
Title="MainWindow" Height="350" Width="525" >
<Window.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:RootNode}" ItemsSource="{Binding Nodes}">
<StackPanel x:Name="spContainer" Orientation="Horizontal">
<TextBlock Text="{Binding Title}"/>
<TextBlock Text="{Binding User.Friend.Friend.Name}"/>
</StackPanel>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding User}" Value="{x:Null}">
<Setter TargetName="spContainer" Property="Background" Value="Yellow"/>
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Node}" ItemsSource="{Binding SubNodes}">
<TextBlock Text="{Binding Title}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:SubNode}" ItemsSource="{Binding Nodes}">
<TextBlock Text="{Binding Title}"/>
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<TreeView ItemsSource="{Binding RootNodes}"/>
</Grid>
</Window>
As you can see you can define a template by data type, you can also use triggers to modify the behavior in specific cases, you could also use som binding converters...
3) You can bind to nested properties just like to normal ones :
<TextBlock Text="{Binding User.Friend.Friend.Name}"/>
However in some cases more than two level bindings could fail (fail to resolve or fail to update when property changes, ...)

DataTemplateSelector and its weird behavior

I am using a DataTemplateSelector inside a ContentControl. I have 3 different DataTemplates based on 3 different object types. When I set the content of my ContentControl to data of the mentioned types, the DataTemplateSelector swaps to the specific DataTemplate AND the selector futhermore seems to rollback/reset the values from the old template. Why is that so?
Edit: I figured out that the values get resetted because I have an attached property caled Prop and inside its OnPropertyChangedCallback it notifies me about the Prop having value null on swapping between DataTemplates. You can see that attached property in code below.
Can somebody help me out what happens behind this swapping mechanism of DataTemplateSelector?
Here is a deeper explaination with code:
public void Window1()
{
InitalizeComponents();
}
public void OnClick(object sender, RoutedEventArgs e)
{
if(this.DataContext == null)
this.DataContext = "Hallo";
else{
if(this.DataContext is string)
this.DataContext = 123;
else{
if(this.DataContext is int)
this.DataContext = null;
}
}
}
By clicking on Button few times I change the type and so in ContentControl the selector changes to DataTemplate.
The selector looks like this below. It swaps between textDataTemplate and numericDataTemplate and one more template. As I mentioned i have those three type which are string, int, and one more, that i wish not to metion. Their DataTemplates are called textDataTemplate, numericDataTemplate and that one more. :)
<local:MyTemplateSelector x:Key="dataTemplateSelector"
TextTemplate="{StaticResource textDataTemplate}"
NumericTemplate="{StaticResource numericDataTemplate}"/>
public class MyTemplateSelector : DataTemplateSelector
{
public DataTemplate TextTemplate;
public DataTemplate NumericTemplate;
public DataTemplate Select(object item, Culture.....)
{
if(item is string)
{
return this.TextTemplate;
}
else
{
return this.NumericTemplate;
}
}
}
ContentControl and XAML looks like this:
<Button Click="OnClick" Content="Click Me"/>
<ContentControl Name="contentCtrl"
Content="{Binding}"
Width="123"
ContentTemplateSelector="{StaticResource dataTemplateSelector}" />
And this is how textDataTemplate looks alike.
<DataTemplate x:Key="textDataTemplate">
<TextBox x:Name="text" my:AttProperties.Prop="{extension:MarkupExt value}" Text="{Binding Path=Txt, Mode=Default, UpdateSourceTrigger=Explicit}"/>
</DataTemplate>
numericDataTemplate looks similar to textDataTemplate just that only digits are allowed.
The Prop is my attached property from AttProperties class of type string. The Prop is somewhere inside of all three DataTemplate. Above the Prop is sitting on a TextBox but it could be a Label too. The markupextension is just a "return Hello". The extension is just there to test how to create a custom markupextension. There is no big deal with the extension. It shouldnt have to do much with the swapping of DataTemplates.
One more time to explain my problem. Swapping seems reselts/rollback my old templates. I swap from textDataTemplate to lets say numericDataTemplate and the Prop of textDataTemplate gets set to null but the value before was "Hello".
Why is that happening? It seems like the same behavior with using tiggers. Once a Trigger is no more valid it rollsback the used values. Is a DataTemplateSelector using some kind of same mechanism as Triggers?
Edited:
The attached property is just a simple .RegisterAttached with an OnPropertyChangedCallback. Inside OnPropertyChangedCallback I figured the prop is null when swapping the dataTemplates.
If you use two-way binding in numeric template and it only accepts something like Double, it can set value to number. But no one can be sure without seeing full code. It's possible that your own code does something wrong.
To understand things better, create your own control, derived from the ContentControl, and use it in your sample. Then override control methods OnContentxxxChanged, insert breakpoints there and debug your application. You should understand, what's going on with your data and with template selector. When application stops on breakpoint, carefully check all values and look at stack trace. To debug bindings you can insert IValueConverters, it would give you place in code, where you can check values.
I really suggest you to make the simplest working thing first, and then go to more complicated things such as textboxes with two-way bindings to some property of some control which you didn't show in your question. Here is a working version with TextBlocks:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public void OnClick(object sender, RoutedEventArgs e)
{
if (this.DataContext == null)
this.DataContext = "Hallo";
else if (this.DataContext is string)
this.DataContext = 123;
else if (this.DataContext is int)
this.DataContext = null;
}
}
public class MyTemplateSelector : DataTemplateSelector
{
public DataTemplate TextTemplate {get; set;}
public DataTemplate NumericTemplate {get; set;}
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is string)
{
return this.TextTemplate;
}
else
{
return this.NumericTemplate;
}
}
}
and xaml:
<Grid>
<Grid.Resources>
<DataTemplate x:Key="numericDataTemplate">
<TextBlock Foreground="Red" Text="{Binding}" />
</DataTemplate>
<DataTemplate x:Key="textDataTemplate">
<TextBlock Foreground="Green" Text="{Binding}"/>
</DataTemplate>
<local:MyTemplateSelector x:Key="dataTemplateSelector"
TextTemplate="{StaticResource textDataTemplate}"
NumericTemplate="{StaticResource numericDataTemplate}"/>
</Grid.Resources>
<StackPanel>
<Button Click="OnClick" Content="Click Me" VerticalAlignment="Top"/>
<ContentControl Name="contentCtrl"
Content="{Binding}"
Width="300" Height="100"
ContentTemplateSelector="{StaticResource dataTemplateSelector}" />
</StackPanel>
</Grid>
Compare with your code. When you inherit from DataTemplateSelector, you should override SelectTemplate method and don't invent methods with other names. All controls such as ContentControl will only use SelectTemplate. Etc..
Obviously, all works and DataTemplateSelector does nothing wrong. I suppose, your problem is somewhere in your data and bindings
And look at your OnClick method - it always sets DataContext to null

Binding on dynamically-added elements

TPTB have decided that our app must run in a single window, popping up new windows in modal mode is not allowed.
And naturally, we have a UI design that involves popping up modal dialogs all over the place.
So I added a top-level Grid to the Window. In that Grid I defined no rows or columns, so everything draws in Row 0/Column 0.
The first element in the Grid was another Grid that contained everything that was normally displayed in the Window. The second was a full-sized Border with a gray, semi-transparent Background. The rest were Borders with wide Margins and white Backgrounds, containing the various UserControls that needed to be displayed as popups. All but the first had Visibility="Collapsed".
And then, when I needed to show a popup, I'd set Visibility="Visible" on the gray background and on the appropriate UserControl. The result was a nice shadowbox effect that worked fine.
Until somebody decided that the popups needed to be able to display popups. In a non-predictable order.
The limitation of the method I had implemented, using Visibility="Collapsed" elements in a Grid was that their order was fixed. UserControlB would always be displayed on top of UserControlA, even if it was UserControlB that asked to have UserControlA displayed. And that's not acceptable.
So my next attempt was to define the various UserControls in Window.Resources, and to add them to the Grid in code:
this.masterGrid.Children.Add(this.Resources["userControlA"] as UserControlA);
And that almost works. But the bindings are all messed up.
As an example, one of the controls is supposed to bind a Property to the CurrentItem of a collection in a member object of the Window's viewmodel. When I had the control defined as an invisible item in the Grid, it worked fine. But when I defined it as a Resource, the Property was null - it was never bound.
So I tried binding it in code, after I added it to the grid:
userControlA.SetBinding(UserControlA.myProperty, new Binding()
{ Source = this.viewModel.myCollection.CurrentItem });
And that compiles and runs just fine, but I'm not binding to the right object.
The first time I display the UserControl, I see the right object bound to it. But when I close it, and move the CurrentItem in the collection to a different object, and display the UserControl again, I still see the first object bound. If I close it again, and open it a third time, then I will see the right object bound to the control.
I've checked in code, and the CurrentItem that I'm binding to is right, every time, but it only seems to take every other time.
So I tried explicitly clearing the binding, first:
BindingOperations.ClearBinding(userControlA, UserControlA.myProperty);
userControlA.SetBinding(UserControlA.myProperty, new Binding()
{ Source = this.viewModel.myCollection.CurrentItem });
But that doesn't seem to have made any difference.
In all, it feels like I'm running down a rabbit hole, chasing deeper and deeper into complexity, to solve what should be a fairly simple problem.
Does anyone have any suggestions as to:
How to get binding to work on dynamically-added elements, or
How to get arbitrarily-ordered popups to display, as shadowboxes, without using dynamically-ordered elements?
Thanks in advance.
While it seems really odd for me that you can't create new Windows, I would definitely recommend not to complicate it too much by doing unnecesary things such as storing your views in the MainWindow's resources.
It would be better if you just added new instances of these elements into an ObservableCollection:
XAML:
<Window x:Class="WpfApplication4.Window8"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication4"
Title="Window8" Height="300" Width="300">
<Window.Resources>
<DataTemplate DataType="{x:Type local:ViewModel1}">
<StackPanel Background="Green">
<TextBlock Text="This is ViewModel1!!"/>
<TextBlock Text="{Binding Text}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModel2}">
<StackPanel Background="Blue" HorizontalAlignment="Center">
<TextBlock Text="This is ViewModel2!!"/>
<TextBlock Text="{Binding Text2}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModel3}">
<StackPanel Background="Red" VerticalAlignment="Center">
<TextBlock Text="This is ViewModel3!!"/>
<TextBlock Text="{Binding Text3}"/>
<TextBox Text="{Binding Text3}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<DockPanel>
<Button Width="100" Content="Add" Click="Add_Click" DockPanel.Dock="Top"/>
<Button Width="100" Content="Remove" Click="Remove_Click" DockPanel.Dock="Top"/>
<ListBox ItemsSource="{Binding ActiveWidgets}" SelectedItem="{Binding SelectedWidget}">
<ListBox.Template>
<ControlTemplate>
<ItemsPresenter/>
</ControlTemplate>
</ListBox.Template>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="VerticalAlignment" Value="Stretch"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<ContentPresenter ContentSource="Content"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</DockPanel>
</Window>
Code Behind:
using System.Linq;
using System.Windows;
using System.Collections.ObjectModel;
using System;
namespace WpfApplication4
{
public partial class Window8 : Window
{
private WidgetsViewModel Widgets { get; set; }
public Window8()
{
InitializeComponent();
DataContext = Widgets = new WidgetsViewModel();
}
private Random rnd = new Random();
private int lastrandom;
private void Add_Click(object sender, RoutedEventArgs e)
{
var random = rnd.Next(1, 4);
while (random == lastrandom)
{
random = rnd.Next(1, 4);
}
lastrandom = random;
switch (random)
{
case 1:
Widgets.ActiveWidgets.Add(new ViewModel1() {Text = "This is a Text"});
break;
case 2:
Widgets.ActiveWidgets.Add(new ViewModel2() { Text2 = "This is another Text" });
break;
case 3:
Widgets.ActiveWidgets.Add(new ViewModel3() { Text3 = "This is yet another Text" });
break;
}
Widgets.SelectedWidget = Widgets.ActiveWidgets.LastOrDefault();
}
private void Remove_Click(object sender, RoutedEventArgs e)
{
Widgets.ActiveWidgets.Remove(Widgets.SelectedWidget);
Widgets.SelectedWidget = Widgets.ActiveWidgets.LastOrDefault();
}
}
public class WidgetsViewModel: ViewModelBase
{
public ObservableCollection<ViewModelBase> ActiveWidgets { get; set; }
private ViewModelBase _selectedWidget;
public ViewModelBase SelectedWidget
{
get { return _selectedWidget; }
set
{
_selectedWidget = value;
NotifyPropertyChange(() => SelectedWidget);
}
}
public WidgetsViewModel()
{
ActiveWidgets = new ObservableCollection<ViewModelBase>();
}
}
public class ViewModel1: ViewModelBase
{
public string Text { get; set; }
}
public class ViewModel2: ViewModelBase
{
public string Text2 { get; set; }
}
public class ViewModel3: ViewModelBase
{
public string Text3 { get; set; }
}
}
Just copy and paste my code in a File - New - WPF Application and see the results for yourself.
Since the Grid always places the last UI Element added to it topmost, you will see that Adding items to the observablecollection makes these "different widgets" always appear on top of each other, with the topmost being the last one added.
The bottom line is, when WidgetA requests to open WidgetB, just create a new WidgetBViewModel and add it to the ActiveWidgets collection. Then, when WidgetB is no longer needed, just remove it.
Then, it's just a matter of putting your UserControls inside a proper DataTemplate for each ViewModel. I strongly suggest you keep a separate ViewModel for each of your Widgets, and if you need to share data between them, just share data between the ViewModels.
Don't attempt to do things like ListBox ItemsSource="{Binding Whatever, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}" unless you have a good reason to.
This way you no longer have to deal with Panel.ZIndex stuff. Maybe you can create a couple of attached properties to deal with things like focus and whatnot, but this approach is dead simple, and by far more performant than the Visibility and the Resources approaches.

ListBox doesn't refresh after property changed

I'm trying to bind two ListBoxes:
<ListBox SelectionChanged="lbApplications_SelectionChanged"
ItemsSource="{Binding Path=Applications,
UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" />
<ListBox DisplayMemberPath="Message"
ItemsSource="{Binding Path=Events,
UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" />
Applications and Events are public properties in Window class.
I set DataContext to this to both list boxes and implement INotifyPropertyChanged in Window class:
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
And then after adding new item to Applications or Events I call:
NotifyPropertyChanged("Events");
NotifyPropertyChanged("Applications");
The issue is that ListBox is loaded only one time. What am I doing wrong?
Let's just look at one of the ListBoxes, since they're both the same, basically.
The code we're concerned about is this:
<ListBox ItemsSource="{Binding Path=Applications,
UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" />
Since you're new to WPF, let me say you probably don't need UpdateSourceTrigger or Mode in there, which leaves us with this:
<ListBox ItemsSource="{Binding Path=Applications}" />
You mentioned that Applications is a public property in your code-behind. You need it to be a DependencyProperty, and you need it to fire events when it changes -- most people use an ObservableCollection for this.
So your code-behind will have something like this:
public ObservableCollection<string> Applications
{
get { return (ObservableCollection<string>)GetValue(ApplicationsProperty); }
set { SetValue(ApplicationsProperty, value); }
}
public static readonly DependencyProperty ApplicationsProperty =
DependencyProperty.Register("Applications",
typeof(ObservableCollection<string>), typeof(Window1),
new UIPropertyMetadata(null));
Then, where you want to add it, you'll do something like this:
this.Applications = new ObservableCollection<string>();
Applications.Add("Whatever");
Finally, for the "simple" binding syntax to work in the XAML, I usually change the DataContext in my Window (or the root Control element for the file, whatever I'm working in) to
<Window DataContext="{Binding RelativeSource={RelativeSource Self}}" ... >
...
Your Applications box will update automatically.
The problem is that your property value hasn't changed. It's still the same list, same reference.
One solution might be that your collections are of type ObservableCollection. These lists provide events for WPF when you add or remove items.

Resources