WPF MVVM combine two collections in one view model - c#

I'm struggling with something and hope somebody can help with a tip on how to accomplish this:
I wrote a UserControl that looks like a File Explorer with on the left side a TreeView with diskdrives and folders (each item has a checkbox) and as soon as a TreeViewItem on the left side is selected, the right side should display the contents of that directory in a ListView, also with a checkbox for each file.
The TreeView is bound in a ViewModel and has many children and subchildren. This works well. The right side however, should only be 1 level of children and no subchildren.
I get the "IsSelected" event within the ViewModel, but of course I then have a reference to the current selected TreeViewItem on the left side, which is totally unrelated to the main level ListView on the right side, which I need to fill with data at that point. How can I reach the main level ListView on the right side, is there any way to create a global lvMain or something that I can access from within the ViewModel or am I appoaching all of this the wrong way and do I need 2 ViewModels instead? Any tips are welcome!
Edit: Here is an image of the desired result, it is the non MVVM version of it, which I am now converting to a control that uses a viewmodel in order to add extra functionality:
enter image description here
Some XAML code snippets:
<TreeView Name="myTreeView" ItemsSource="{Binding tvChildren}" ItemTemplate="{StaticResource CheckBoxItemTemplate}" Height="Auto" Width="Auto"></TreeView>
...
<ListView x:Name="myListView" FontWeight="Normal" ItemsSource="{Binding lvChildren}" Width="Auto" HorizontalAlignment="Stretch">
UserObject code snippets:
public FileExplorer()
{
InitializeComponent();
try
{
// Initially fill TreeView (left side of FileExplorer) with local drives:
DriveInfo[] drives = DriveInfo.GetDrives();
ViewModel dummyNode = null;
int i = 0;
foreach (DriveInfo drive in drives)
{
if (drive.DriveType != DriveType.CDRom)
{
ViewModel node = new ViewModel();
tvMainNode.tvChildren.Add(node);
node.Text = drive.Name;
node.FullDir = drive.Name;
node._parent = tvMainNode;
// Add a blanc child so the toggle buttons are generated:
tvMainNode.tvChildren[i].tvChildren.Add(dummyNode);
i++;
}
}
}
catch { return; }
DataContext = this;
}
ViewModel code snippet:
private readonly ObservableCollection<ViewModel> _tvChildren = new ObservableCollection<ViewModel>();
private readonly ObservableCollection<ViewModel> _lvChildren = new ObservableCollection<ViewModel>();
void SetIsSelected(bool value)
{
if (value == _isSelected) { return; }
_isSelected = value;
// This is obviously not working:
ViewModel test = new ViewModel();
this.lvChildren.Clear();
test.Naam = "bla";
this.lvChildren.Add(test);
}

Related

How can I implement a carousel of images in WPF where the selected item is always the first one?

I am creating a WPF application to act as a front end for a video games library and I'm attempting to mimic the Netflix UI. One of the features in this application is to cycle through images of games to select which game you want to play.
The desired behavior is different than the behavior when arrowing through the items in a ListBox: when you arrow through items in a ListBox, your selection moves up and down. The behavior I'm looking to implement is that as you arrow through the items, the selected item is always at the first position and the items are cycling across the selector. The term for this would be a carousel where the selected item is at index 0.
I've implemented this poorly and to give some context, here's a picture of how my interface currently looks:
My current implementation
To achieve this, I believe what I should do is extend the StackPanel class or maybe implement my own Panel. But details on custom panels are a bit complicated and hard to come by. I want to show what I've done to this point to get this working but I'm very unhappy with these implementations and I'd like to get some advice on what direction I should go for a proper implementation.
Here are some details on what I've tried.
The screenshot above is a result of a GameList class that I created which implements INotifyPropertyChanged and includes properties for 15 different games.
private GameMatch game0;
public GameMatch Game0
{
get { return game0; }
set
{
if (game0 != value)
{
game0 = value;
PropertyChanged(this, new PropertyChangedEventArgs("Game0"));
}
}
}
private GameMatch game1;
public GameMatch Game1
{
get { return game1; }
set
{
if (game1 != value)
{
game1 = value;
PropertyChanged(this, new PropertyChangedEventArgs("Game1"));
}
}
}
// identical code for games 2-10
private GameMatch game11;
public GameMatch Game11
{
get { return game11; }
set
{
if (game11 != value)
{
game11 = value;
PropertyChanged(this, new PropertyChangedEventArgs("Game11"));
}
}
}
private GameMatch game12;
public GameMatch Game12
{
get { return game12; }
set
{
if (game12 != value)
{
game12 = value;
PropertyChanged(this, new PropertyChangedEventArgs("Game12"));
}
}
}
I've laid the images out in my XAML and added enough so that they will run off the edge of the screen:
<StackPanel Name="GameImages" Orientation="Horizontal">
<Border BorderThickness="2" BorderBrush="AntiqueWhite">
<Image Name="Image_Game1" Source="{Binding CurrentGameList.Game1.FrontImage}"/>
</Border>
<Image Source="{Binding CurrentGameList.Game2.FrontImage}"/>
<!-- identical images for games 3-10 -->
<Image Source="{Binding CurrentGameList.Game11.FrontImage}" />
<Image Source="{Binding CurrentGameList.Game12.FrontImage}" />
</StackPanel>
I implemented a ListCycle class which can take any arbitrary list and a count of items that you want to cycle. In case it helps, here's the code for the ListCycle. It takes care of cycling the lists by tracking the index of items in list that should be displayed on screen in a given position.
public class ListCycle<T>
{
// list of games or whatever you want
public List<T> GenericList { get; set; }
// indexes currently available to display
// will cycle around the size of the generic list
public int[] indices;
public ListCycle(List<T> genericList, int activeCycleCount)
{
GenericList = genericList;
indices = new int[activeCycleCount];
InitializeIndices();
}
private void InitializeIndices()
{
if (GenericList != null)
{
int lastIndex = -1;
for (int i = 0; i < indices.Length; i++)
{
indices[i] = GetNextIndex(lastIndex);
lastIndex = indices[i];
}
}
}
private int GetNextIndex(int currentIndex)
{
currentIndex += 1;
if (currentIndex == GenericList.Count)
{
currentIndex = 0;
}
return currentIndex;
}
private int GetPreviousIndex(int currentIndex)
{
currentIndex -= 1;
if (currentIndex == -1)
{
currentIndex = GenericList.Count - 1;
}
return currentIndex;
}
public int GetIndexValue(int index)
{
return indices[index];
}
public T GetItem(int index)
{
return GenericList[indices[index]];
}
public void CycleForward()
{
for (int i = 0; i < indices.Length; i++)
{
if (i + 1 < indices.Length - 1)
{
indices[i] = indices[i + 1];
}
else
{
indices[i] = GetNextIndex(indices[i]);
}
}
}
public void CycleBackward()
{
for (int i = indices.Length - 1; i >= 0; i--)
{
if(i - 1 >= 0)
{
indices[i] = indices[i - 1];
}
else
{
indices[i] = GetPreviousIndex(indices[i]);
}
}
}
}
So when you press right, I cycle forward and reset the game images. When you press left, I cycle backward and reset the game images. The RefreshGames method takes care of updating all of those game properties in my game list.
private void RefreshGames()
{
Game0 = gameCycle.GetItem(0);
Game1 = gameCycle.GetItem(1);
Game2 = gameCycle.GetItem(2);
Game3 = gameCycle.GetItem(3);
Game4 = gameCycle.GetItem(4);
Game5 = gameCycle.GetItem(5);
Game6 = gameCycle.GetItem(6);
Game7 = gameCycle.GetItem(7);
Game8 = gameCycle.GetItem(8);
Game9 = gameCycle.GetItem(9);
Game10 = gameCycle.GetItem(10);
Game11 = gameCycle.GetItem(11);
Game12 = gameCycle.GetItem(12);
}
This approach works but it doesn't work well. It's not dynamic, it doesn't scale well and it doesn't perform all that well. Arrowing through images one at a time performs just fine but trying to quickly move through them, is a bit slow and feels clunky. It's not a very good user experience.
I tried a second approach, using a listbox bound the to my list of games. And to cycle the games to the left, I would remove the first item from my list of games and insert it at the end of the list. To go to the right, I would remove the item at the end of the list and insert it at index 0. This also worked but it didn't perform very well either.
So I'm looking for suggestions on a better way to implement this that would give better performance (smoother scrolling) and be more dynamic (i.e. this approach may not work well on an ultrawide monitor - 12 games may not be enough depending on the widths of the images). I'm not looking for anyone to solve it for me but point me in the right direction as I'm very new to WPF.
My feeling is that I should be extending the stack panel class and changing the way you cycle through the items or maybe creating my own panel. Can anyone confirm if this is the best approach and if so point me to some good resources to help me understand how to create a custom panel that changes the way navigation is done? I've been reading articles on creating custom panels to try to get my head around that process.
Being new to WPF, I want to make sure I'm not going down a rabbit hole or trying to reinvent a wheel that already exists. So the question is whether a custom panel is the right approach to solving this problem?
I believe what I should do is extend the StackPanel class
WPF encourages composition of existing Controls over inheritance; in your case inheriting the StackPanel looks too complicated for your purpose when you could achieve the same with your second approach:
I would remove the first item from my list of games and insert it at the end of the list
This indeed looks more like idiomatic WPF, especially if you try to follow the MVVM design pattern.
or maybe creating my own panel
This is not an easy step especially if you're new to WPF but that would be very interesting for you. That could be a way to go, especially if you internally rely on a StackPanel (composition) instead of inheriting from it.
Example implementation with an ItemsControl
I will use an ItemsControl which can display a collection of data for you (in your case, you have some GameMatch).
First define the data behind the interface, ie a collection of GameMatch. Let's give each GameMatch a name and a variable IsSelected which tells if the game is selected (ie in first position). I'm not showing the INotifyPropertyChanged implementation but it should be there for both properties.
public class GameMatch : INotifyPropertyChanged {
public string Name { get => ...; set => ...; }
public bool IsSelected { get => ...; set => ...; }
}
Your carousel interface is interested in a collection of GameMatch, so let's create an object to model this.
Our graphical interface is gonna bind to the Items property to display the collection of games.
It is also gonna bind to the two commands that are implemented such as to shift the list to the left or to the right. You can use the RelayCommand to create commands. In a nutshell, a Command is simply an action that gets executed and that you can easily refer to from your interface.
public class GameCollection {
// Moves selection to next game
public ICommand SelectNextCommand { get; }
// Moves selection to previous game
public ICommand SelectPreviousCommand { get; }
public ObservableCollection<GameMatch> Items { get; } = new ObservableCollection<GameMatch> {
new GameMatch() { Name = "Game1" },
new GameMatch() { Name = "Game2" },
new GameMatch() { Name = "Game3" },
new GameMatch() { Name = "Game4" },
new GameMatch() { Name = "Game5" },
};
public GameCollection() {
SelectNextCommand = new RelayCommand(() => ShiftLeft());
SelectPreviousCommand = new RelayCommand(() => ShiftRight());
SelectFirstItem();
}
private void SelectFirstItem() {
foreach (var item in Items) {
item.IsSelected = item == Items[0];
}
}
public void ShiftLeft() {
// Moves the first game to the end
var first = Items[0];
Items.RemoveAt(0);
Items.Add(first);
SelectFirstItem();
}
private void ShiftRight() {
// Moves the last game to the beginning
var last = Items[Items.Count - 1];
Items.RemoveAt(Items.Count - 1);
Items.Insert(0, last);
SelectFirstItem();
}
}
The key here is the ObservableCollection class which will tell the view whenever it changes (for example, everytime we move items around inside it) so the view will update to reflect this.
Then, the view (your XAML) should specify how to display the collection of games. We're gonna use an ItemsControl laying out items horizontally:
<StackPanel>
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Margin="10" Background="Beige" BorderBrush="Black" Width="150" Height="50">
<Border.Style>
<Style TargetType="Border">
<Setter Property="BorderThickness" Value="1" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="true">
<Setter Property="BorderThickness" Value="5" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock Text="{Binding Name}"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Content="Previous" Command="{Binding SelectPreviousCommand}"/>
<Button Content="Next" Command="{Binding SelectNextCommand}"/>
</StackPanel>
</StackPanel>
Notice the ItemsControl ItemsSource="{Binding Items}" which tells the ItemsControl to display all the objects in the Items property. The ItemsControl.ItemsPanel part tells to lay them out in an horizontal StackPanel. The ItemsControl.ItemTemplate part explains how each game should be displayed, and the DataTrigger within tells WPF to increase the border thickness for the selected item. Finally, the StackPanel at the bottom displays two Button which call SelectPreviousCommand and SelectLeftCommand in our GameCollection.
Finally, you should set the DataContext of the whole thing to a new GameCollection:
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
DataContext = new GameCollection();
}
}
From there you can customize the UI as you'd like.
Animations and smooth scrolling
That is a whole other topic but you could for example trigger a translation animation of all your items when clicking one of the buttons.
I'll try and point you in the right direction. If you haven't already checked it out, I would try to make your application follow the MVVM pattern. In your case, the ViewModel would have an ObservableCollection of "Games". You would then bind your ItemsControl's source to that collection.
As far as getting the carousel to work, I think the path you will want to go down is creating a custom ItemsControl or ListBox. You can override the styling and create some custom behavior to get the carousel to work how you would like it to.
I can probably help out more if you have a more specific question.

WPF accessing scrollviewer of a listview codebehind

I need to access the scrollviewer of a listview from the codebehind.
here is the definition of my listview
<ListView Grid.Row="1" ItemsSource="{Binding Path=SpecList, UpdateSourceTrigger=PropertyChanged}"
Name="mylistview"
ItemTemplate="{StaticResource SpecElementTemplate}"
Background="{StaticResource EnvLayout}"
ScrollViewer.HorizontalScrollBarVisibility="Visible"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ItemContainerStyle="{StaticResource MyStyle}"
BorderBrush="Blue"
BorderThickness="20"
Margin="-2">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
How can I get the scrollviewer?
Thank you
Andrea
There are several ways to get the ScrollViewer. Simplest solution is to get the the first child of the first child of the ListView. This means get the Border and the ScrollViewer inside this Border like described in
this answer:
// Get the border of the listview (first child of a listview)
Decorator border = VisualTreeHelper.GetChild(mylistview, 0) as Decorator;
// Get scrollviewer
ScrollViewer scrollViewer = border.Child as ScrollViewer;
A second way is to scan all childrens recursive to find the ScrollViewer. This is described in the answer by Matt Hamilton in this question. You can simply use this function to get the ScrollViewer.
ScrollViewer scrollViewer = GetChildOfType<ScrollViewer>(mylistview);
This second solution is much more generic and will also work if the template of your ListView was edited.
Use VisualTreeHelper class to access any child control.
Psudeo code to your case:
//Declare a scroll viewer object.
ScrollViewer sViewer = default(ScrollViewer );
//Start looping the child controls of your listview.
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(YOUR_LISTVIEW_OBJECT.VisualParent ); i++)
{
// Retrieve child visual at specified index value.
Visual childVisual = (Visual)VisualTreeHelper.GetChild(YOUR_LISTVIEW_OBJECT.VisualParent , i);
ScrollViewer sViewer = childVisual as ScrollViewer;
//You got your scroll viewer. Stop looping.
if (sViewer != null)
{
break;
}
}
I also suggest using the CollectionChanged event. In this code, the CollectionChanged event handler is added to the codebehind after the view model has been loaded. Then, each time the collection changes we scroll to the bottom of the listview. Here is an important point. The scrollviewer child of the list view might not yet be completely rendered when our events start firing. Hence we will get exceptions if we try to use the VisualTreeHelper.GetChild method. So, we have to first attempt to get the scrollviewer and then ignore its positioning if it is not yet available.
private void ReceivedItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
// Make sure the items source property in the viewmodel has some items
if (myViewModel.ReceivedItems.Count > 0)
{
var aScrollViewer = RcvdListView.GetChildOfType<ScrollViewer>();
// Make sure the scrollviewer exists before trying to position it
if (aScrollViewer != null)
{
aScrollViewer.ScrollToBottom();
}
}
}
Listview's ScrollViewer should be accessible after LayoutUpdated. You could hook on LayoutUpdated and then get if from Visual tree
private static void ListView_LayoutUpdated(object sender, EventArgs e)
{
var listview = (ListView)sender;
var viewer = listview.GetFirstChildOfType<ScrollViewer>();
}
public static T GetFirstChildOfType<T>(this DependencyObject dependencyObject) where T : DependencyObject
{
if (dependencyObject == null)
{
return null;
}
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(dependencyObject); i++)
{
var child = VisualTreeHelper.GetChild(dependencyObject, i);
var result = (child as T) ?? GetFirstChildOfType<T>(child);
if (result != null)
{
return result;
}
}
return null;
}

Datagrid sort, what happened to row?

I am coding a UserControl that can be use as a ScrollBar with color mark on it (like mostly all IDE).
My UserControl look
<UserControl x:Class="MarkedScrollBar" x:Name="Instance" ...>
<Grid Name="grd" >
<COLUMN/ROW DEF>...</>
<ScrollViewer Name="scrView" Grid.RowSpan="3" Grid.ColumnSpan="2" >
<my:IcuContentPresenter x:Name="presenter" Content="{Binding AdditionnalContent, ElementName=Instance}" ContentUpdated="presenter_ContentUpdated" />
</ScrollViewer>
<Canvas ...(Canvas place on the scrollbar) />
</Grid>
</UserControl>
On my UserControl, everything work fine. I can add content in my IcuContentPresenter (Extension of ContentPresenter to get 1 more event).
In my main window i use my MarkedScroolBar like this :
<my:IcuMarkedScrollBar Grid.Row="0" x:Name="bbb" Grid.Column="0" >
<my:IcuMarkedScrollBar.AdditionnalContent>
<my:IcuDataGrid ItemsSource="{Binding AAA, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" Loaded="DataGrid_Loaded" Width="300" LoadingRow="DataGrid_LoadingRow" />
</my:IcuMarkedScrollBar.AdditionnalContent>
</my:IcuMarkedScrollBar>
Again, i have a DataGrid extension to get 1 event (Sorted event, code from here).
Now my problem is that when i sort my datagrid Inside my MarkedScrollBar, i want the mark to follow the content, that the mark is binding to.
First attempt, I try to link the mark with the control having a Dictionary Key=Mark, Value=Control. I was getting the DatagridRow that I wanted to have a mark for my datacontext.
First time the datagrid appear, my mark are all well place, but if I sort, my mark don't follow. I went into debugging and saw that there is a DataGridRow that contains my datacontext item, but it's not the one i save in my Dictionary.
End First attempt
Second attempt, with the DataGridRow changing when I sort, I thought that if I was linking my DataContext item to my mark it would fix the bug.
So now, when I sort my datagrid, I try to find the datagridrow that contains my datacontext item. But it does find anything.
But, when i reexecute the same code manually with a button in the window, it's work. All my mark appear at the good position.
End second attempt
Brief explanation of my extension :
DataGrid : I use it to get the Sorted event, because in Sorting event the row where not already move.
ContentPresenter : I add event on it to get modification to the content, but not to the Property (like datagrid sorting). For that i needed to check the type of the content, so i don't support everything for now.
My code for the ContentPresenter is that :
public class IcuContentPresenter : ContentPresenter
{
public event EventHandler<ControlEventArgs> ContentUpdated;
public IcuContentPresenter()
{
this.Loaded += OnLoaded;
}
protected void OnLoaded(object p_oSender, RoutedEventArgs p_oEvent)
{
Type t = this.Content.GetType();
if (t == typeof(IcuDataGrid))
{
IcuDataGrid oControl = this.Content as IcuDataGrid;
oControl.Sorted += CallEvent;
}
}
void CallEvent(object sender, EventArgs e)
{
if (ContentUpdated != null)
{
ContentUpdated(this, new ControlEventArgs(Content.GetType()));
}
}
public UIElement FindVisualElementForObject(object p_oObject)
{
UIElement oTheOne = null;
if (Content.GetType() == typeof(IcuDataGrid))
{
oTheOne = FindInDatagrid(p_oObject);
}
return oTheOne;
}
private UIElement FindInDatagrid(Object p_oObj)
{
DataGrid oGrid = Content as DataGrid;
var a = (DataGridRow)oGrid.ItemContainerGenerator.ContainerFromItem(p_oObj);
return a;
}
//----------------------------------------------------
// Inner class
//----------------------------------------------------
public class ControlEventArgs : EventArgs
{
public ControlEventArgs(Type value)
{
ControlType = value;
}
public Type ControlType { get; set; }
}
}
To support a control i will have to add them here. On the load of this control, I check the content, if the content is my Datagrid on the event Sorted i call the my Event ContentUpdated to be able to update my mark. But at this moment, it's like my datagrid contains no row with my datacontext item.
When virtualization is enabled, row elements will only be generated for data items which are actually in view. You will need to associate the marks with the underlying data items (row objects) rather than the DataGridRow elements. The Items property on the DataGrid should have the rows in their correctly sorted positions.

Listbox shows elements wrongly after scrolling

In my windows phone 8.1 application I have a listbox when initially loading everything I want to show in the listbox everything is fine. However after scrolling some of the elements will be shown incorrectly. This seems completely random.
The forementioned listbox looks like this in xaml:
<ListBox Name="MainPage_List" Grid.Column="0" Background="#EDEDED" >
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Components:MyUserControl />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
As you can see its datatemplate is linked to a usercontrol. In this UserControl I have a DataContextChanged event. Which looks like this:
private void DataContextChanged(object sender, object e)
{
if (mySource == null)
{
mySource = DataContext as Message;
}
if (mySource != null)
{
if (mySource.Source_Type == SourceTypes.Type1)
{
MyGrid.Visibility = Visibility.Collapsed;
MyOtherGrid.Visibility = Visibility.Visible;
}
else if (mySource.Source_Type == SourceTypes.Type2)
{
MyOtherGrid.Visibility = Visibility.Collapsed;
MyGrid.Visibility = Visibility.Visible;
}
}
}
I check multiple types and variables here and depending on that I set other things visible or load other images. This works fine. However when scrolling through the list sometimes some of the elements will be shown differently than they should. Even when I make sure the code that decides which elements will be shown is not used again.
The source of the list is a custom class enheriting from ObservableCollection using a custom class that enherits from INotifyPropertyChanged.
Does anyone know what I am doing wrong here? Or why this happens and how to get around this?
After some discussion with WereWolfBoy we found the problem.
It is related to the default ItemsPanel of the ListBox which is a VirtualizingStackPanel. It reuses the controls from the ItemTemplate of the ListBox and changes their DataContext when necessary to display different items. Unlike normal StackPanel, it does NOT create separate controls for different items.
For this to work, the items must refresh when the DataContext changes. And here's the actual problem. Because of this code:
if (mySource == null)
{
mySource = DataContext as Message;
}
the DataContext was actually loaded just once, and subsequent changes did not affect the UI. Removing the if and getting the DataContext every time it changes fixed the issue.
This may be a result of virtualization.
If your list is long enough, there is a reuse of controls for items in it.
Imagine you have a list with 1000 items in it, but the listbox only has 30 control instances that are re-used when you scroll up and down.
If this is the case, you will see the wrong behavior repeatin ever X items.
In order to solve this problem, I would recommend that instead of using DataContextChanged, you should expose a property as DependencyProperty.
In your data template, bind to this property and this will do the trick for you.
public bool ShowMyGrid
{
get { return (bool)GetValue(ShowMyGridProperty); }
set { SetValue(ShowMyGridProperty, value); }
}
public static readonly DependencyProperty ShowMyGridProperty =
DependencyProperty.Register("ShowMyGrid", typeof(bool), typeof(MyUserControl1), new UIPropertyMetadata(false, ShowMyGridCallback));
static void ShowMyGridCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var myControl = d as MyUserControl1;
bool newVal = (bool)e.NewValue;
if (newVal)
{
myControl.MyGrid.Visibility = Visibility.Collapsed;
myControl.MyOtherGrid.Visibility = Visibility.Visible;
}
else
{
myControl.MyGrid.Visibility = Visibility.Visible;
myControl.MyOtherGrid.Visibility = Visibility.Collapsed;
}
}

WPF Listbox with checkboxes appearing blank, added dynamically

I'm trying to populate a listbox with a series checkbox entries, however once running the code below the listbox has blank entries in it, which are selectable, i.e. a blue bar appears. However neither the text or checkbox appears.
for (int num = 1; num <= 10; num++)
{
CheckBox checkBox = new CheckBox();
checkBox.Text = "sheet" + num.ToString();
checkBox.Name = "checkbox" + num.ToString();
thelistbox.Items.Add(checkBox);
}
The best way to handle this is to create a list of data -- in your case, a list of numbers (or a list of strings (sheet1, sheet2, etc). You can then assign that list of numbers to thelistbox.ItemsSource. Inside the XAML of your listbox, set the ItemTemplate to include a CheckBox and bind the number to the text of the checkbox.
Try changing
checkBox.Text = "sheet" + num.ToString();
to
checkBox.Content = "sheet" + num.ToString();
With that change, I was able to use your example successfully.
To follow up on Brian's comment, here is an outline of a simple checkbox list in C# wpf. This will need more code to handle checking/unchecking boxes and general post-interaction handlers. This setup presents the difference in elements on two lists of objects (defined elsewhere) in a checkbox list.
The XAML
...
<ListBox Name="MissingNamesList" ItemsSource="{Binding TheMissingChildren}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<CheckBox Content="{Binding Path=Name}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
...
The supporting C# code:
...
public partial class MissingNamesWindow : Window
{
// Make this accessible from just about anywhere
public ObservableCollection<ChildName> TheMissingChildren { get; set; }
public MissingNamesWindow()
{
// Build our collection so we can bind to it later
FindMissingChildren();
InitializeComponent();
// Set our datacontext for this window to stuff that lives here
DataContext = this;
}
private void FindMissingChildren()
{
// Initialize our observable collection
TheMissingChildren = new ObservableCollection<ChildName>();
// Build our list of objects on list A but not B
List<ChildName> names = new List<ChildName>(MainWindow.ChildNamesFromDB.Except(
MainWindow.ChildNamesFromDisk).ToList());
// Build observable collection from out unique list of objects
foreach (var name in names)
{
TheMissingChildren.Add(name);
}
}
}
...
Hope that clarifies a bit.

Categories