WPF UserControl weird binding problem - wpf

Im usign a Ribbon Window and in the "content area beneath" I have a grid in which I will be displaying UserControls. To demonstrate my problem lets take a look at this simple UserControl:
<ListView x:Name="lvPersonList">
<ListView.View>
<GridView>
<GridViewColumn Width="120" Header="Name" DisplayMemberBinding="{Binding Name}"/>
<GridViewColumn Width="120" Header="Height" DisplayMemberBinding="{Binding Height}"/>
</GridView>
</ListView.View>
</ListView>
And the code:
public partial class MyUserControl: UserControl
{
private List<Person> personList;
public TestSnpList()
{
InitializeComponent();
this.personList = new List<Person>();
this.personList.Add(new Person { Name = "Chuck Norris", Height = 210 });
this.personList.Add(new Person { Name = "John Rambo", Height = 200 });
this.lvPersonList.ItemsSource = personList;
}
}
public class Person
{
public string Name { get; set; }
public int Height { get; set; }
}
The parent Window:
<Grid x:Name="grdContent" DockPanel.Dock="Top">
<controls:MyUserControl x:Name="myUserControl" Visibility="Visible"/>
</Grid>
I don't understant why this binding doesn't work. Instead of values (Name and Height) I get full class names. If I use this code in a Window it works fine.
Any ideas? I would like this user contorl works for itself (it gets the data form the DB and represents it in a ListView)...
Thanks!

It seems the problem is with RibbonWindow.
If I use Window and UserControl binding works fine, but if I use RibbonWindow (Odyssey Ribbon) binding doesn't work. What I don't understand is that in design mode I can see proper values and in running mode I see only class names:
http://i977.photobucket.com/albums/ae255/HekoSLO/designModeVSrunning.png

Related

WFP C# ListView databinding for dynamic updates

Help please.
I am updating an application that needs to display alarm data dynamically in a ListView.
Currently when data arrives the alarm condition is evaluated and displayed graphically
in XAML controls using DependencyProperties in Custom UserControls and INotifyPropertyChanged
events in the MainViewModel. This works fine to change the color of the fields
as required with the updates pulled through live.
My task is to implement some alarm logic such as user acknowledgment of active
alarms etc. To this end I'm writing the alarm details to a LocalDB instance and
displaying them in a ListView page. This page populates ok when I load up the
dB with a _Loaded event when I navigate to the page however when on the page I
can't seem to get the data to update live using the same method that updates the
graphic colors!
I have created class to hold LV-style string data that populates from the Db so
there shouldn't be an issue with that. I don't want to post too much code,
but can anyone tell me if I should be populating each field of the ListView with
individual data bindings or can I rely a data binding that can be passed the whole
LV alarm class?
public class LVAlarmItem
{
public string Name { get; set; }
public string Status { get; set; }
public string Raised { get; set; }
public string Id { get; set; }
public string Ack { get; set; }
}
public ObservableCollection<Alarm> AlarmList = new ObservableCollection<Alarm>(); //Real alarms from Db
public ObservableCollection<LVAlarmItem> LVAlarmItems = new ObservableCollection<LVAlarmItem>();
// LV items populated from Db
foreach (Alarm alarm in _viewModel.AlarmManager.AlarmList)
{
lVAlarmItems.Add(new AlarmManager.LVAlarmItem() { Name = alarm.AlarmName, Status = alarm.ValueStatus.ToString(), Raised = alarm.RaisedTimeStamp.ToString(), Id = alarm.Id.ToString(), Ack = alarm.Ack.ToString() } );
}
AlarmList.Alarm_ListView.ItemsSource = lVAlarmItems;
This XAML works for the _loaded event:
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="300" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Status" Width="300" DisplayMemberBinding="{Binding Status}" />
<GridViewColumn Header="Raised" Width="300" DisplayMemberBinding="{Binding Raised}" />
<GridViewColumn Header="Id" Width="250" DisplayMemberBinding="{Binding Id}" />
<GridViewColumn Header="Acknowledged" Width="200" DisplayMemberBinding="{Binding Ack}" />
</GridView>
</ListView.View>
It feels like my LV should be populated directly with Alarms to be honest, but I thought I'd try to get it working with the simpler structure first. New to MVC which doesn't help, hope my question makes sense.
More info: I'm trying to bind my main XAML page to the user control:
<Controls:AlarmList x:Name="AlarmList" MainAlarmList="{Binding MainAlarmList}" Width="1350" Margin="60,63,100,100" Background="{x:Null}" Loaded="AlarmList_Loaded" Height="600" />
where in the cs of the user control I have is :
{
public static readonly DependencyProperty MainAlarmListProperty = DependencyProperty.Register(
"MainAlarmList", typeof(ObservableCollection<AlarmManager.LVAlarmItem>), typeof(AlarmList), new PropertyMetadata(OnPropertyChanged));
public void Render()
{
}
public ObservableCollection<AlarmManager.LVAlarmItem> MainAlarmList
{
get { return (ObservableCollection<AlarmManager.LVAlarmItem>)GetValue(MainAlarmListProperty); }
set { SetValue(MainAlarmListProperty, value); }
}
Screenshot:Link

Find item in listview with gridview and WPF

I am looking for a way to know if my listview contains a value. Below is my code.
public class OnlineUserList
{
public string Name { get; set; }
public string Color { get; set; }
}
<ListView x:Name="lvOnlineUsers" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" SelectionMode="Single" SelectionChanged="lvOnlineUsers_SelectionChanged">
<ListView.View>
<GridView x:Name="lvOnlineUsersGridView" AllowsColumnReorder="False">
<GridViewColumn Header="Online Users" Block.TextAlignment="Center" TextOptions.TextFormattingMode="Display" TextBlock.FontWeight="Bold">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Name="tbOnlineUsersGridView" Text="{Binding Path=Name}" Foreground="{Binding Path=Color}" HorizontalAlignment="Center" VerticalAlignment="Center" TextOptions.TextFormattingMode="Display" Style="{StaticResource ResourceKey=lblLabel}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
public void AddUserToList(string username)
{
lvOnlineUsers.Items.Add(new OnlineUserList { Name = username, Color = "Black" });
}
Now this is where am having issue
public void RemoveUserFromList(string username)
{
if(lvOnlineUsers.Items.Contains(username))
lvOnlineUsers.Items.Remove(username);
}
You should learn MVVM.
In the mean time, put the items in an ObservableCollection and assign it to the listview's ItemsSource property in your codebehind. Thereafter, repeat after me: Never, ever touch lvOnlineUsers.Items. Never, never, never. Forget that it exists. Everything you do, you interact with the ObservableCollection. Search it, add items to it, remove items from it. The UI will magically and mysteriously update itself.
I'm going to assume this is in MainWindow. If this is in a different view, the constructor will have a different name.
public MainWindow()
{
InitializeComponent();
lvOnlineUsers.ItemsSource = _onlineUsers;
}
private ObservableCollection<OnlineUserList> _onlineUsers
= new ObservableCollection<OnlineUserList>();
public void AddUserToList(string username)
{
_onlineUsers.Add(new OnlineUserList { Name = username, Color = "Black" });
}
public void RemoveUserFromList(string username)
{
// We don't search _onlineUsers for the string username, because
// _onlineUsers doesn't contain strings. It contains your user class.
// So instead, we look for the user class instance that has the name
// we want.
var found = _onlineUsers.FirstOrDefault(ou => ou.Name == username);
if (found != null)
{
_onlineUsers.Remove(found);
}
}
Until you have looked into MVVM, try this:
for(int i = lvOnlineUsers.Items.Count - 1; i >= 0; --i)
{
OnlineUserList item = lvOnlineUsers.Items[i] as OnlineUserList;
if (item != null && item.Name == username)
lvOnlineUsers.Items.Remove(lvOnlineUsers.Items[i]);
}

ListView binding in WPF not showing expected collection items

Why isn't this binding working?
<Window x:Class="S3PackageInstaller.MainWindow" x:Name="Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s3u="clr-namespace:S3PackageInstaller"
Icon="App.ico" Title="Sims 3 Package Installer" Height="480" Width="740">
<DockPanel LastChildFill="True">
<DockPanel DockPanel.Dock="Left" Width="200" VerticalAlignment="Stretch" LastChildFill="True"
Margin="20,20,0,20">
<!-- this is the binding that isn't working -->
<ListView Width="200" ItemsSource="{Binding ElementName=Window1, Path=InstalledPackages}">
<ListView.View>
<GridView>
<GridViewColumn Header="Installed Packages" DisplayMemberBinding="{Binding Filename}"></GridViewColumn>
</GridView>
</ListView.View>
</ListView>
<!-- snip -->
</Window>
Relevant code behind:
public partial class MainWindow : Window
{
public ObservableCollection<object> InstalledPackages { get; private set; }
public MainWindow()
{
InitializeComponent();
InstalledPackages = new ObservableCollection<object>();
LoadInstalledPackages();
}
private void LoadInstalledPackages()
{
var installPath = Settings.Default.TargetDirectory;
var packages = System.IO.Directory.GetFiles(installPath, "*.package");
InstalledPackages.Clear();
foreach (string filename in packages)
InstalledPackages.Add(new { Filename = filename });
}
// snip...
}
When I run the program, the ListView is empty. When debugging, I have verified that the collection contains items after LoadInstalledPackages is run.
I think the problem is that while your collection property is an ObservableCollection, so will notify of collection changes, the property itself does not raise a change notification when you first assign to it. When you create your window as follows:
public MainWindow()
{
InitializeComponent();
InstalledPackages = new ObservableCollection<object>();
LoadInstalledPackages();
}
When InitializeComponent is invoked your UI is created and bindings constructed. At this point InstalledPackages is null. In the next line you create your collection, but because InstalledPackages does not raise a PropertyChanged event, your binding is not updated.
Either implement INotifyPropertyChanged, or assign the ObservableCollection to this property before invoking InitializeComponent.

How to toggle scroll lock in a WPF ListView?

How to toggle scroll lock in a WPF ListView?
When more items are added to a ListView, than there is space to show the following should happen depending of the scroll lock state.
When scroll lock is enabled the ListView should not scroll when adding more items (this is the default behavior).
When scroll lock is disabled the ListView should automatically scroll to the bottom so the newly added items are visible to the user.
The scroll lock state should be controlled by the (seldom used) 'scroll lock' button on a typical keyboard.
EDIT: A bit of code...
<ListView x:Name="logMessagesListView" ItemsSource="{Binding ElementName=self, Path=LogMessages}">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Created" Width="100" DisplayMemberBinding="{Binding Created}"/>
<GridViewColumn Header="Level" Width="80" DisplayMemberBinding="{Binding LogLevel}"/>
<GridViewColumn Header="Message" Width="350" DisplayMemberBinding="{Binding Message}"/>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
I would keep the log messages in an ObservableCollection, both for automatic UI notifications and the CollectionChanged event. Once a new item is added, check if the button is clicked. If it is, move to the last item (or you can use the index/item properties of the event arguments).
You're going to need to add System.Windows.Forms to the project references so you can check the button state.
public partial class MainWindow : Window
{
private ObservableCollection<LogMessage> logMessages;
public MainWindow()
{
this.logMessages = new ObservableCollection<LogMessage>();
/* add/load some data */
this.logMessages.CollectionChanged += new NotifyCollectionChangedEventHandler(this.LogMessages_CollectionChanged);
this.LogMessages = CollectionViewSource.GetDefaultView(this.logMessages);
InitializeComponent();
}
public ICollectionView LogMessages
{
get;
set;
}
private void LogMessages_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
if (System.Windows.Forms.Control.IsKeyLocked(System.Windows.Forms.Keys.Scroll))
{
this.LogMessages.MoveCurrentToLast();
}
}
}
}
public class LogMessage
{
public string Created
{ get; set; }
public string LogLevel
{ get; set; }
public string Message
{ get; set; }
}
Put ScrollViewer.CanContentScroll="False" in your XAML and that should work!

master detail binding in WPF

Why don't I see the details in this example. I cannot change the structure of dataclass, master class and detail class. So I have to solve this with the correct binding.
public class ViewModel
{
public dataclass data { get; set; }
public ViewModel()
{
data = new dataclass();
master a_master = new master();
a_master.mastername = "hello";
detail a_detail = new detail();
a_detail.detailname = "goodbye";
data.details.Add(a_detail);
data.Add(a_master);
}
}
public class dataclass : ObservableCollection<master>
{
public ObservableCollection<detail> details { get; set; }
public dataclass()
{
details = new ObservableCollection<detail>();
}
}
public class master
{
public string mastername { get; set; }
}
public class detail
{
public string detailname { get; set; }
}
And in my XAML I am binding like this:
<Window x:Class="md.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:md.viewmodels"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<vm:ViewModel/>
</Window.DataContext>
<StackPanel Orientation="Vertical" >
<ListView ItemsSource="{Binding Path=data}">
<ListView.View>
<GridView>
<GridViewColumn Header="master" DisplayMemberBinding="{Binding mastername}"/>
</GridView>
</ListView.View>
</ListView>
<ListView ItemsSource="{Binding Path=data/details}">
<ListView.View>
<GridView>
<GridViewColumn Header="detail" DisplayMemberBinding="{Binding detailname}"/>
</GridView>
</ListView.View>
</ListView>
</StackPanel>
</Window>
Try
<ItemsSource="{Binding Path=data.details}">
instead of
<ItemsSource="{Binding Path=data/details}">
I think what you tried to achieve was kind of a master/detail scenario with binding to hierarchical data like decribed in How to: Use the Master-Detail Pattern with Hierarchical Data. In fact, as long as you have an ObservableCollection<details> as property of a class derived from ObservableCollection<master> this is not hierarchical, and hence the / in the binding expression won't work. See PropertyPath XAML Syntax, section Source Traversal (Binding to Hierarchies of Collections) for details about the /.
Also there are widely accepted conventions for capitalization in C#, saying that you should use Pascal casing for public types like the classes and properties here.

Resources