Moving items in ItemsControl [duplicate] - c#

This question already has answers here:
WPF 4 Drag and Drop with visual element as cursor
(2 answers)
Closed 1 year ago.
I have a list of items, which every item include a few of notes, like this:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<Item> items = new List<Item>();
Item item1 = new Item() { Title = "First Item" };
Item item2 = new Item() { Title = "Second Item" };
item1.ItemNotes.Add(new ItemNote() { Note = "first_note" });
item1.ItemNotes.Add(new ItemNote() { Note = "second_note" });
item1.ItemNotes.Add(new ItemNote() { Note = "third_note" });
item2.ItemNotes.Add(new ItemNote() { Note = "fourth_note" });
item2.ItemNotes.Add(new ItemNote() { Note = "fifth_note" });
items.Add(item1);
items.Add(item2);
ic_tasks.ItemsSource = items;
}
}
public class Item
{
public Item()
{
this.ItemNotes = new ObservableCollection<ItemNote>();
}
public string Title { get; set; }
public ObservableCollection<ItemNote> ItemNotes { get; set; }
}
public class ItemNote
{
public string Note { get; set; }
}
In XAML I present those items by ItemsControl, like this:
<Grid>
<ItemsControl Name="ic_tasks">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:Item}">
<StackPanel Orientation="Vertical">
<Border BorderThickness="1" CornerRadius="8" Background="DarkGray">
<StackPanel Orientation="Horizontal" Height="30">
<TextBox Text="{Binding Title}" Margin="5,5,5,5"/>
<Button Margin="5,5,5,5">Fold</Button>
</StackPanel>
</Border>
<ItemsControl ItemsSource="{Binding ItemNotes}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:ItemNote}">
<TextBox Margin="40,5,5,5" Text="{Binding Note}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
And now my goal is to make those Items moveable. For example, I would like to be able to move the second item over the first item, like below:
What I tried?
I try to wrap all ItemsControl by Canvas and bind position by Canvas.Top & Canvas.Left by using Mouse Events, but there's no such property for StackPanel which I want to move. There's only such propery for ItemsControl, like below:
<Canvas x:Name="canvas_of_items">
<ItemsControl Name="ic_tasks" Canvas.Top="{Binding YPosition}" Canvas.Left="{Binding XPosition}">
<...>
<StackPanel Orientation="Horizontal" Height="30" PreviewMouseDown="StackPanel_MouseDown" PreviewMouseMove="StackPanel_MouseMove" PreviewMouseUp="StackPanel_MouseUp">
<...>
</ItemsControl>
</Canvas>
So my question is:
What are the options to allow Items to be moved between themselves? Importantly, when moving the Item, it should be visible, not the mouse cursor itself.

You can set the ItemsControl to place your items in a canvas using ItemsControl.ItemsPanel like this:
<ItemsControl Name="ic_tasks">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas></Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
But to move the items you cant just set the Canvas properties on the StackPanel because they are surrounded by ContentPresenter. Instead you use ItemsControl.ItemContainerStyle to set a style on these ContentPresentors and there you can do something like this:
<Style>
<Setter Property="Canvas.Left" value="<<somthing - propebly a binding so it will be easy to change>>"
</Style>

Related

wpf - listview inside of tabcontrol

I'm having difficulties to Binding a ListView inside of a TabControl
Bellow is my ViewModel
public ObservableCollection<TabItem> Tabs { get; set; }
public class TabItem
{
public string Header { get; set; }
public ObservableCollection<abc> Content { get; set; }
}
public class abc
{
public string text { get; set; }
}
this is how I initialize it
InitializeComponent();
Tabs = new ObservableCollection<TabItem>()
{
new TabItem {
Header = "Header1",
Content = new ObservableCollection<abc>()
{
new abc{ text = "content1" },
new abc{ text = "content2" },
}
},
new TabItem {
Header = "Header2",
Content = new ObservableCollection<abc>()
{
new abc{ text = "content3" },
new abc{ text = "content4" },
}
}
};
tabControl1.ItemsSource = Tabs;
and here is how I populate and Bind it in the view
<TabControl Name="tabControl1">
<TabControl.ItemTemplate>
<!-- this is the header template-->
<DataTemplate>
<TextBlock Width="500" Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<!-- this is the body of the TabItem template-->
<DataTemplate>
<ListView Margin="10" ItemsSource="{Binding Content}">
<ListViewItem>
<StackPanel>
<TextBlock Text="{Binding text}" />
</StackPanel>
</ListViewItem>
</ListView>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
but then I got an errror saying that
InvalidOperationException: Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead.
How can I bind a ListView inside a TabControl?
Each Tab control will have the same format of ListView. The Actual ListView will have more than 1 field. not only text field.
Where did I do it wrongly?
ListViewItems are created by ListView for each element in ItemsSource. To change their look you need ItemTemplate:
<ListView Margin="10" ItemsSource="{Binding Content}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding text}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
if you have more than 1 field, not only text field, define GridView with columns like shown in this post

TabItem Content IsEnabled binding

I'm creating a WPF application using MVVM pattern (at least I'm trying). There is <TabControl> with bound ItemsSource,which is an ObservableCollection<TabModel> Tabs. Tabs has Name and Items property, where Items is a list of ControlModel, which means Controls. I have problem with binding IsEnabled property to Grid where Items are placed.
Below there is a part of my code presenting the way I'm doing this:
private ObservableCollection<TabModel> tabs;
public ObservableCollection<TabModel> Tabs
{
get
{
if (tabs == null)
{
tabs = new ObservableCollection<TabModel>();
RefreshTabs();
}
return tabs;
}
set
{
tabs = value;
OnPropertyChanged("Tabs");
}
}
\\Tab Model
public string Name { get; set; }
private List<ControlModel> items;
public List<ControlModel> Items
{
get { return items; }
set
{
items = value;
OnPropertyChanged("Items");
}
}
And xaml...
<TabControl Margin="0,100,0,0" ItemsSource="{Binding Tabs,UpdateSourceTrigger=PropertyChanged}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ScrollViewer VerticalScrollBarVisibility="Hidden">
<Grid Margin="5,5,5,5" IsEnabled="{Binding IsProductEditionEnabled}">
<!--<Grid Margin="5,5,5,5">-->
<ItemsControl ItemsSource="{Binding Items,UpdateSourceTrigger=PropertyChanged}" ItemTemplateSelector="{StaticResource ControlTemplateSelector}"/>
</Grid>
</ScrollViewer>
</DataTemplate>
</TabControl.ContentTemplate>
The part...
<Grid Margin="5,5,5,5" IsEnabled="{Binding IsProductEditionEnabled}">
is not working. There is no error. This grid is always disabled. By default it's false.
private bool isProductEditionEnabled = false;
public bool IsProductEditionEnabled
{
get { return isProductEditionEnabled; }
set
{
isProductEditionEnabled = value;
OnPropertyChanged("IsProductEditionEnabled");
}
}
The question is : How to bind IsEnabled in my case properly?
You are inside a DataTemplate so you need to specify where the parent DataContext is when you do the binding, something like this:
<DataTemplate>
<ScrollViewer VerticalScrollBarVisibility="Hidden">
<Grid IsEnabled="{Binding Path=DataContext.IsProductEditionEnabled,
RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}">
</Grid>
</ScrollViewer>
</DataTemplate>

WPF stackpanel does not show the whole content

I am using a Combobox to show the contents of two different Lists, the List<Production> productions and the List<Seasons> seasons. Below is the XAML code
<StackPanel Grid.Column=" 1" Grid.Row="2">
<ComboBox x:Name="comboBox" ItemsSource="{Binding ShowProductionsSeasonsNames}" SelectedItem="{Binding SelectedProductionsSeasons}">
<ComboBox.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" FontWeight="Bold" FontSize="14"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ComboBox.GroupStyle>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
And this is the code in the viewmodel.
public ListCollectionView ShowProductionsSeasonsNames
{
get
{
List<ProductionsSeasonsNames> items = new List<ProductionsSeasonsNames>();
foreach (var production in this.Productions)
{
items.Add(new ProductionsSeasonsNames() { Name = production.DisplayName, Category = "Productions" });
}
foreach (var seasons in this.Seasons)
{
items.Add(new ProductionsSeasonsNames() { Name = seasons.DisplayName, Category = "Seasons" });
}
ListCollectionView lcv = new ListCollectionView(items);
lcv.GroupDescriptions.Add(new PropertyGroupDescription("Category"));
return lcv;
}
}
public class ProductionsSeasonsNames
{
public string Name { get; set; }
public string Category { get; set; }
}
My issue is that it partly works. Although my productions and seasons lists contain around 100 items each, in the stackpanel only 15 from each list appear. Furthermore when I scroll down or up the focus goes directly to the top of each header without letting me to actually scroll (I am sorry I cant explain this part better). Does anyone have any ideas on this?
Use Grid instead of Stackpanel and set grid height="*" or you can directly put your combobox into Grid.Column=" 1" Grid.Row="2"

Binding to TextBlock in ListView

I don't know if my understanding of binding is just poor or if I am not seeing the problem, but I hope someone can help me out here. I have a ListView with a template of an image and a TextBlock, I need the TextBlock to be bound to the ItemsSource of the ListView. However when I run this I get nothing shown, I don't even see my image that I have set.
XAML:
<UserControl.Resources>
<FontFamily x:Key="FontFamily">MS Reference Sans Serif</FontFamily>
</UserControl.Resources>
<Grid>
<ListView BorderThickness="0" ItemsSource="{Binding Facies}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="../Images/Shale.png"/>
<TextBlock Text="{Binding FaciesName}" Width="75" Margin="5"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
C#:
public partial class FaciesControl : UserControl
{
public FaciesControl()
{
InitializeComponent();
}
public List<string> Facies {get; set;}
public void Bind(string[] data)
{
Facies = new List<string>();
Facies.AddRange(data);
}
}
First set DataContext like this:
public FaciesControl()
{
InitializeComponent();
string[] str = { "Name1", "Name2", "Name3" };
Bind(str); // Make sure you have called the Bind method
DataContext = Facies;
}
Second change your XAML like this:
<ListView BorderThickness="0" ItemsSource="{Binding}">
....
....
<TextBlock Text="{Binding}" Width="75" Margin="5"/>

Bind Collection to StackPanel

I want to take a collection of objects and bind it to a StackPanel so basically if the collection has 4 elements, inside the stack panel that should produce 4 buttons lets say.
I tried this...But I dont think its the correct approach anyway. I used DataTemplated to do this type of idea in the past.. please correct me if I am wrong.
Here is my fake model
public class MockModel
{
public ObservableCollection<MockNode> Nodes;
public MockModel()
{
Nodes = new ObservableCollection<MockNode>();
}
}
public class MockNode
{
public MockNode()
{
}
private string itemname;
public string ItemName
{
get { return this.itemname; }
set { this.itemname = value; }
}
}
In code I set the DataContext like this...
// Init Model
MockModel myModel = new MockModel();
for (int i = 0; i < 4; i++)
{
MockNode mn = new MockNode();
mn.ItemName = String.Format("Node {0}", i);
myModel.Nodes.Add(mn);
}
// Set DataContext for StackPanel
Stack.DataContext = myModel.Nodes;
And the xaml
<StackPanel x:Name="tStack">
<ItemsControl ItemsSource="{Binding Nodes}">
<ItemsControl.Template>
<ControlTemplate>
<Button Content="{Binding ItemName}"/>
</ControlTemplate>
</ItemsControl.Template>
</ItemsControl>
</StackPanel>
IT does bind but instead of 4 buttons I only get one button....
Ideas?
Alright I have figured it out... Using an ItemsControl solved the problem...
<ItemsControl x:Name="tStack" ItemsSource="{Binding Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding ItemName}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

Categories