WPF layout and specific "data-binding" - c#

I'm writing a WPF 95% GUI application, and need a specific shortcut-icon behaviour.
One image is sometimes better than (lots of) words, so here's the basic layout:
(as a new user, couldn't attach an image...)
http://imageshack.us/f/705/appb.jpg/
-Every icons in the bottom sub menu represents a control "page" in the app - FIXED icons.
-The icons on the left are shortcuts - recent and favourites - according to clicks on the bottom icons.
-I'm using stack-panels as containers, and the number of items is fixed.
-every control set has it's own context-menu.
What I'm trying to accomplish is as follows:
When the user clicks an item in the bottom menu, I want it to "magically appear" in the "Recent" panel. When user clicks "remove" (context menu) at the "Recent" panel, I need the (right-clicked) icon to go away.
Right now my (working) solution is unbelievably cumbersome, and I'm sure an elegant one exists..
Would very much appreciate any advice,
Daniel.

I assume you're using the MVVM pattern for this? If not, you should be.
So, assuming you're using MVVM, would something like this work for you:
class BottomPanelViewModel
{
public BottomPanelViewModel()
{
Items = new ObservableCollection<PageViewModel>();
ItemsView = new ListCollectionView(Items);
ItemsView.CurrentChanged += SelectionChanged;
}
public ObservableCollection<PageViewModel> Items { get; private set; }
public ListCollectionView ItemsView { get; private set; }
}
class RecentPanelViewModel
{
public RecentPanelViewModel()
{
Items = new ObservableCollection<PageViewModel>();
}
public ObservableCollection<PageViewModel> Items { get; private set; }
}
class WindowViewModel
{
public WindowViewModel()
{
BottomPanel = new BottomPanelViewModel();
RecentPanel = new RecentPanelViewModel();
BottomPanel.CurrentChanged += (s, e) =>
{
RecentPanel.Items.Add(BottomPanel.ItemsView.CurrentItem);
};
}
public BottomPanelViewModel BottomPanel { get; private set; }
public RecentPanelViewModel RecentPanel { get; private set; }
}
In your window constructor, create a WindowViewModel instance and use it as your DataContext:
public Window()
{
InitializeComponent();
DataContext = new WindowViewModel();
}
Then in your XAML you can bind to the properties of the WindowViewModel:
<Window ...>
<DockPanel>
<ListBox DockPanel.Dock="Bottom"
ItemsSource="{Binding BottomPanel.ItemsView}"
IsSynchronizedWithCurrentItem="True"/>
<ListBox DockPanel.Dock="Left"
ItemsSource="{Binding RecentPanel.Items}"/>
</DockPanel>
</Window>
Explanation: the WindowViewModel contains a BottomPanelViewModel and a RecentPanelViewModel. Each contain an ObservableCollection of Items, and the bottom panel also exposes a collection view. The collection view allows us to track the current selection in the UI.
I'm using simple ListBoxes in the example XAML, but you can use whatever ItemsControl you like.
When the selection changes in the bottom panel, the window view model hears this and adds the selected item to the recent panel's ObservableCollection. You'll obviously want to add logic here to check for duplicates etc.

Related

WPF Listbox not populated with items from ObservableCollection

The main-window is listening for plugging in/out USB-devices. If it is an usb-key/disk it collects a file-list from that device and show that list in a second window.
While debugging I can see that the NewUsbFiles observablecollection get's populated with 117 items. I see that the property UsbFile (before calling the showdialog) has 117 items, but nevertheless the listbox is empty.
Any thoughts ?
The method to populate / create that second window:
NewUsbFiles = new ObservableCollection<UsbFile>();
UpdateNewUsbFiles(driveName);
Application.Current.Dispatcher.Invoke(delegate
{
var usbFileSelector = new UsbFileSelector()
{
Owner = this,
UsbFiles = NewUsbFiles
};
usbFileSelector.ShowDialog();
});
The UsbFile-class:
public class UsbFile
{
public string UsbFileName { get; set; }
public string OnTableFileName { get; set; }
public bool Ignored { get; set; } = false;
public UsbFile(string fileName)
{
var fileInfo = new FileInfo(fileName);
UsbFileName = fileInfo.FullName;
OnTableFileName = $"{fileInfo.CreationTime:yyMMddHHmmsss}_{fileInfo.Name}";
}
}
The XAML of the second window :
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:MainWindow="clr-namespace:PartyPictures.WPF.MainWindow" x:Name="wUsbFileSelector"
x:Class="PartyPictures.WPF.UsbFileSelector"
mc:Ignorable="d"
Title="USB" HorizontalAlignment="Center" VerticalAlignment="Center" WindowStyle="ToolWindow" ScrollViewer.VerticalScrollBarVisibility="Auto" SizeToContent="WidthAndHeight">
<StackPanel x:Name="spUsbFileList">
<ListBox x:Name="ImageListbox"
DataContext="{Binding ElementName=wUsbFileSelector}"
ItemsSource="{Binding UsbFiles}"
Background="AliceBlue" ScrollViewer.HorizontalScrollBarVisibility="Disabled" MinWidth="200" MinHeight="200">
</ListBox>
</StackPanel>
</Window>
The code-behind of the second window :
public partial class UsbFileSelector : Window
{
public ObservableCollection<UsbFile> UsbFiles { get; set; } = new ObservableCollection<UsbFile>();
public UsbFileSelector()
{
InitializeComponent();
}
}
Inside the window you can see InitializeComponent method. It creates all of the stuff defined in XAML and applies all bindings. After binding has been appplied with your empty collecton (that you have created with default property value) the binding will not know about any change of that property, that was the right answer.
But implementing INotifyPropertyChanged is more about viewmodel instances, not visual.
I really suggest you use Dependency Property for windows and controls if you want to bind. There are some reasons for that:
Dependency property setter has built-in notify mechanism.
If you bind one DP to another DP, value is shared in between.
After all, it is WPF approach =)
Here is how your window will look like after change
public partial class UsbFileSelector : Window
{
public static readonly DependencyProperty UsbFilesProperty =
DependencyProperty.Register("UsbFiles", typeof(ObservableCollection<UsbFile>), typeof(UsbFileSelector));
public ObservableCollection<UsbFile> UsbFiles
{
get { return (ObservableCollection<UsbFile>) GetValue(UsbFilesProperty); }
set { SetValue(UsbFilesProperty, value); }
}
public UsbFileSelector()
{
InitializeComponent();
}
}
Also I strongly recommend you to use some WPF inspector tool while developing for the WPF, for example, snoop. You can navigate through the controls and properties while app is running and find issues much quickly you can from the code or from stackoverflow =)
In
var usbFileSelector = new UsbFileSelector()
{
Owner = this,
UsbFiles = NewUsbFiles
};
you are assigning a new value to the UsbFiles property without firing a property change notification for that property.
You could either implement INotifyPropertyChanged and fire the PropertyChanged event or make UsbFiles a dependency property.
Or you pass NewUsbFiles as constructor argument and assign it before calling InitializeComponent
public UsbFileSelector(ObservableCollection<UsbFile> usbFiles)
{
UsbFiles = usbFiles;
InitializeComponent();
}
and call it like this:
var usbFileSelector = new UsbFileSelector(NewUsbFiles)
{
Owner = this
};
Note that if you always pass a new collection, using ObservableCollection isn't actually necessary. You never add or remove elements to/from the collection, so there is no need for a change notification.
Someone posted (and deleted the comment) that I should add
DataContext = this;
To
public UsbFileSelector()
{
InitializeComponent();
DataContext = this;
}
Someone else mentioned (that comment too was deleted) that this was not necessary because of the
DataContext="{Binding ElementName=wUsbFileSelector}"
in the XAML.
BUT it turned out that removing the DataContext line from the XAML and setting it to this in code was the sollution. No idea why but that did it.
EDIT just to make clear that this is not a good solution and works only by accident, try the following:
// this works
var usbFileSelector = new UsbFileSelector();
usbFileSelector.Owner = this;
usbFileSelector.UsbFiles = NewUsbFiles;
usbFileSelector.ShowDialog();
// this does not
var usbFileSelector = new UsbFileSelector();
usbFileSelector.Owner = this;
await Task.Delay(10);
usbFileSelector.UsbFiles = NewUsbFiles;
usbFileSelector.ShowDialog();
All the answers already given are correct, the heart of your problem is the
UsbFiles = NewUsbFiles
which causes the binding to "break" - UsbFiles is no longer pointing to the collection that is bound to the Listbox.
Another possible way to solve this would be to simply leave the bound collection alone and just repopulate the contents.
var usbFileSelector = new UsbFileSelector()
{
Owner = this,
UsbFiles.Clear();
foreach (var uf in NewUsbFiles) {
UsbFiles.Add(uf);
}
};

Bind object properties to a datagrid in WPF

I have the following class:
public class Sp3dItem
{
public Sp3dItem()
{
Items= new ObservableCollection<Sp3dItem>();
}
public string OID
{
get;
set;
}
public string Name
{
get;
set;
}
public string Type
{
get;
set;
}
public ObservableCollection<Sp3dItem> Items
{
get;
set;
}
}
I need to show the properties of an instance of this object to a Datagrid (or any other type of grid). Like the Properties Window in Visual Studio. But there are certain properties that I don't care, like 'Items', I only need to show properties of string Type, and only the ones with non empty values (this last one would be a plus, not a real need).
The question is, can I do something like this with binding or do I have to assembly the data on the grid manually?
Sounds like you want a property grid to view the properties of a single object instance, where each property/value pair is a 'row', yes? If that's the case, look into some of the third-party Property Grid controls. The WPF Extended Toolkit has a free one.
Typically, these grids can automatically discover the properties of the target object, and you can choose to hide certain properties by adorning them with [Browsable(false)].
Yes... it's possible and easy once you figure out how the built-in binding wizard works.
This example is for a
<Label...
Create a static instance to your view model in the View. By doing this the designer will show the properties of the Viewmodel in the
properties page once you start "wiring up the bindings"...
//in code behind
public static string Error
{
get { return _Error; }
set { _Error = value; }
}
Now click on the XMAL component in designer just once.
<Label Grid.Row="2" <=Click here one time
In the properties page, click the icon (small square on far right side of property) to start the binding process
Select "Create Data Binding"
Select 'FindAncestor' then the MainWindow of interest, and finally the static property.
Click ok and the bindings are set in XAML Automatically.
<Label Grid.Row="2"
Content="{
Binding Error,
RelativeSource={
RelativeSource FindAncestor,
AncestorType={x:Type local:MainWindow}}}"/>
The verbosity above just says:
Look in MainWindow's static properties for Error.
Make this Label's content that value.
If you want you can also edit the template for the Datagrid, but that's not relevant to your question.

Advice on Views navigation using Caliburn.Micro MVVM WPF

I'm new on Caliburn Micro and want some advice on which path to take to devolop my app interface and navigation between views.
My idea is to have a MainWindow which will contain a menu of buttons, each one related with a specific view. Each view will be stored in a separated WPF UserControl. The mainWindow will also contain a TabControl bound to an ObservableCollection of tabs on viewmodel. Everytime a button on menu is clicked, I want to add a new tab with a ContentPresenter inside that will dynamically load a view and its corresponding viewmodel.
So my questions:
1) Should I use a Screen Collection here?
2) Should the UserControl implement Screen interface?
3) How do I tell MainWindow ViewModel which view to load on the new added tab maintaining viewmodels decoupled?
Thanks to everyone in advance.
UPDATE
After a lot of reading and some help of the community I managed to resolve this. This is the resultant AppViewModel:
class AppViewModel : Conductor<IScreen>.Collection.OneActive
{
public void OpenTab(Type TipoVista)
{
bool bFound = false;
Screen myScreen = (Screen)Activator.CreateInstance(TipoVista as Type);
myScreen.DisplayName = myScreen.ToString();
foreach(Screen miItem in Items)
{
if (miItem.ToString() == myScreen.ToString())
{
bFound = true;
ActivateItem(miItem);
}
}
if (!bFound) ActivateItem(myScreen);
}
public ObservableCollection<MenuItem> myMenu { get; set; }
public ObservableCollection<LinksItem> myDirectLinks { get; set; }
public ICommand OpenTabCommand
{
get
{
return new RelayCommand(param => this.OpenTab((Type) param), null);
}
}
public AppViewModel()
{
OpenTab(typeof(ClientsViewModel));
MenuModel menu = new MenuModel();
myMenu = menu.getMenu();
myDirectLinks = menu.getLinks();
}
public void CloseTab(Screen param)
{
DeactivateItem(param, true);
}
}
I have to keep the ICommand from OpenTabCommand because the name convention of Caliburn.micro doesn't seems to work inside DataTemplate. Hope it could help someone else. Thanks to all
I've done something very similar using Caliburn.Micro, and based it on the SimpleMDI example included with the examples, with a few tweaks to fit my needs.
Much like in the example, I had a main ShellViewModel:
public class ShellViewModel : Conductor<IScreen>.Collection.OneActive
{
}
with a corresponding ShellView containing a TabControl - <TabControl x:Name="Items">, binding it to the Items property of the the Conductor.
In this particular case, I also had a ContextMenu on my ShellView, bound (using the Caliburn.Micro conventions), to a series of commands which instantiated and Activated various other ViewModels (usually with a corresponding UserControl, using the ActivateItem method on the Conductor.
public class YourViewModel: Conductor<IScreen>.Collection.OneActive
{
// ...
public void OpenItemBrowser()
{
// Create your new ViewModel instance here, or obtain existing instance.
// ActivateItem(instance)
}
}
In that case, I didn't require the ViewModels to be created with any particular dependency, or from any other locations in the program.
At other times, when I've needed to trigger ViewModel from elsewhere in the application, I've used the Caliburn.Micro EventAggregator to publish custom events (e.g. OpenNewBrowser), which can be handled by classes implementing the corresponding interface (e.g. IHandle<OpenNewBrowser>), so your main ViewModel could have a simple Handle method responsible for opening the required View:
public class YourViewModel: Conductor<IScreen>.Collection.OneActive, IHandle<OpenNewBrowser>
{
// ...
public void Handle(OpenNewBrowser myEvent)
{
// Create your new ViewModel instance here, or obtain existing instance.
// ActivateItem(instance)
}
}
This section of the documentation will probably be useful, especially the Simple MDI section.
Additional code I mentioned in the comments:
I sometimes use a generic method along these lines ensure that if I have an existing instance of a screen of a particular type, switch to it, or create a new instance if not.
public void ActivateOrOpen<T>() where T : Screen
{
var currentItem = this.Items.FirstOrDefault(x => x.GetType() == typeof(T));
if (currentItem != null)
{
ActivateItem(currentItem);
}
else
{
ActivateItem(Activator.CreateInstance<T>());
}
}
Used like:
public void OpenBrowser()
{
this.ActivateOrOpen<BrowserViewModel>();
}

Initialize control in control

This is my custom control class:
// Extended ComboBox where ill change some property.
public class ExtComboBox : ComboBox
{
...
}
// ExtButton is a control that i am going to drop on Form from ToolBox.
public partial class ExtButton : Button
{
public ExtComboBox ComboBoxInsideButton { get; set; }
public ExtButton()
{
InitializeComponent();
ComboBoxInsideButton = new ExtComboBox();
ComboBoxInsideButton.Text = "hi!";
Controls.Add(ComboBoxInsideButton);
}
}
Basically when i add this control to form there will be ComboBox on top off Button.
Don't ask my why i need this :D
Now if i need to change ComboBox text i simply use:
extButton1.ComboBoxInsideButton.Text = "aaa";
All work fine.. but :) when i am trying to change some ComboBox properties in Design mode (Window Properties -> Expand ComboBoxInsideButton -> Change Text to "bbb")
after rebuilding or running project ComboBox properties will be reseted (ExtButton.Designer.cs)
Question 1: How to initialize subcontrol with some default properties value, so when ill drop control on Form all setting will be added?
and
Question 2: How to change properties of subcontrol on design time.
EDIT:
Answer here: Designer does not generate code for a property of a subcontrol. Why?
Adding [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] solves the problem.
I wrote a mini-tut on how to create custom UserControls and accessing their members here. Pretty much, it looks like you are going to want add properties to your ExtComboBox that expose the ComboBox properties you'd like to change. Then, in ExtButton, you will be able to use the . to change these values at runtime.
Also, instead of doing:
public ExtComboBox ComboBoxInsideButton { get; set; }
...
ComboBoxInsideButton = new ExtComboBox();
do
public ExtComboBox comboBoxInsideButton = null;
...
comboBoxInsideButton = new ExtComboBox();
Be sure to understand the difference between private and public also. I'm not sure if you want your ExtComboBox to be public if you're placing it on another control.
Hope this helps.

Binding a class to a WPF Treeview

I have an object, which holds an observable collection
Im trying to wrap my head around how to stick this all into a treeview.
So the main object would be a parent and items in observable collection are sub-children.
Currently the class has public string property which could be bound to a header.
Here are parts of the class:
public class ServerObject : INotifyPropertyChanged
{
private string _serverName;
ObservableCollection<string> _instanceList;
public ObservableCollection<string> InstanceList
{
get { return _instanceList; }
}
public string ServerName
{
get { return _serverName; }
set
{
_serverName = value;
RaisePropertyChanged("ServerName");
}
}
public ServerObject(string name, string version)
{
ServerName = name;
_instanceList = new ObservableCollection<string>();
}
}
Thanks in advance.
The easiest way to do it is with HierarchicalDataTemplates. Define two or more templates. In the template declaration line add a type indicator for your object. Also, add an ItemsSource attribute which points to the next level down.
<HierarchicalDataTemplate Datatype="{x:Type local:mySerberObject}" ItemsSource="{Binding InstanceList}"/>
Bind the top level collection to the treeview, and you should be off and running. Style the datatemplates to suit your taste.
If you are currently using MVVM (or if you plan to start using it) check out the link below for a really good article about using the treeview with MVVM.

Categories

Resources