We figured out yesterday how to get a listbox's contents to switch between different panels I had stacked on top of each other. I'm attempting to do the same thing in WPF this time, obviously the syntax is different. The code worked 100% correctly in the windows form. I've tried a few different ways to try to get the now "grids" to show, but to no avail.
Thanks in advance!
Current code 'attempt'. I'm just demonstrating a couple ways I've attempted to change the code there in that first "case".
private void listBox1_SelectedIndexChanged(object sender, SelectionChangedEventArgs e)
{
// set the listboxselected item to a string variable
string curItem = listBox1.SelectedItem.ToString();
curItem = listBox1.SelectedItem.ToString();
// variable changes depening on mouse click, sets to whichever string value is selected
switch (curItem)
{
case "General":
gridGeneral.Visibility == true;
gridRightClick.Visibility = Visibility.Visible;
gridSnaps.Visibility = Visibility.Hidden;
break;
case "E-Snaps":
gridGeneral.Visibility = Visibility.Hidden;
gridRightClick.Visibility = Visibility.Hidden;
gridSnaps.Visibility = Visibility.Visible;
break;
case "Mouse":
gridGeneral.Visibility = Visibility.Hidden;
gridRightClick.Visibility = Visibility.Visible;
gridSnaps.Visibility = Visibility.Hidden;
break;
}
Here is the code that works in a windows form
private void listBox1_SelectedIndexChanged(object sender, System.EventArgs e)
{
// set the listboxselected item to a string variable
string curItem = listBox1.SelectedItem.ToString();
curItem = listBox1.SelectedItem.ToString();
// variable changes depening on mouse click, sets to whichever string value is selected
switch(curItem)
{
case "General" :
panel1.Visible = false;
panel2.Visible = true;
panel3.Visible = false;
panel4.Visible = false;
panel5.Visible = false;
break;
etc etc etc....
You wouldn't do this the same way in WPF that you would in WinForms
In WPF, you'll probably have a single control in WPF where in WinForms you have 3, and the Template that control uses to render will change based on the SelectedItem of your ListBox
Most likely the control definition will look something like this, so that the content of it is bound to the ListBox.SelectedItem:
<ContentControl Content="{Binding ElementName=listBox1, Path=SelectedItem}" />
And you can either use ContentTemplates or DataTemplates to tell WPF how to draw that ContentControl's ContentTemplate.
If the SelectedItem is a custom class, a DataTemplate would probably be easier, however since its a string in your example, a ContentTemplate is probably better.
Here's an example of a style for that ContentControl which changes the ContentTemplate property based on the value of the Content
<Style TargetType="{x:Type ContentControl}">
<!-- // Default Template -->
<Setter Property="ContentTemplate" Value="{StaticResource GeneralTemplate}" />
<!-- // Change template depending on a property -->
<Style.Triggers>
<Trigger Property="Content" Value="ESnaps">
<Setter Property="ContentTemplate" Value="{StaticResource ESnapsTemplate}" />
</Trigger>
<Trigger Property="Content" Value="Mouse">
<Setter Property="ContentTemplate" Value="{StaticResource MouseTemplate}" />
</Trigger>
</Style.Triggers>
</Style>
(I may have the syntax of the exact binding you need wrong here... will probably need some testing)
Related
I'm programming in WPF(C#). I want to alert users about filling empty text box (or any other controls). I want to flash control to alert him/her. This is the codes that I used them but it does not change color:
static void AlertByChangingBackground(Control control)
{
Action a = () =>
{
control.Background = System.Windows.Media.Brushes.Red;
Thread.Sleep(500);
control.Background = System.Windows.Media.Brushes.White;
};
control.Dispatcher.Invoke(a);
}
As it can be seen, I also use Action but it does not work. I also use control.UpdateLayout() before Sleep method but it does not working, too. How can I fix the problem.
Update 1:
Now, I use codes illustrated below. But the problem is when the function is called several times (specially when it is called continuously in short times) the color of text does not back to its first color. For example my control may be remain at red color. How can I fix it?
public static void AlertByChangingBackground(Control control)
{
Action a = () =>
{
ColorAnimation animation;
animation = new ColorAnimation();
animation.From = Colors.Red;
animation.To = ToColor(control.Background);
animation.Duration = new Duration(new TimeSpan(0, 0, 0, 0, 330));
RepeatBehavior rb = new RepeatBehavior(3);
animation.RepeatBehavior = rb;
control.Background = new SolidColorBrush(Colors.Red);
control.Background.BeginAnimation(SolidColorBrush.ColorProperty, animation);
};
control.Dispatcher.BeginInvoke(a);
}
I note that I want to start animation from current background of my control, not from white or any predefined color.
You can use triggers or animations to alert the user rather than using thread,
you can add xmlns:sys="clr-namespace:System;assembly=mscorlib" namespace for checking the string is empty or not.
<Style TargetType="{x:Type TextBox}" x:Key="AlertStyle">
<Style.Triggers>
<DataTrigger Binding="{Binding Text,RelativeSource={RelativeSource Mode=Self}}" Value="{x:Static sys:String.Empty}">
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
use this style for your TextBox control like follows,
<TextBox Width="100" Height="25" Style="{StaticResource AlertStyle}">
Im using MVVM architecture and I want to change the row color in a datagrid.
The color of row depends on the item from the model.
so far I have this Code:
private void DataGrid_LoadingRow(object sender, DataGridRowEventArgs e) {
Log4NetLog dataGridRow = e.Row.DataContext as Log4NetLog;
if (highlight) {
if (dataGridRow != null) {
e.Row.Background = new SolidColorBrush(
dataGridRow.LogColour.Colour);
}
} else {
e.Row.Background = new SolidColorBrush(Colors.White);
}
}
As you can see, in the second Line I have to make an reference to a Log4NetLog which is in the model.
So how can I change the code to adapt the MVVM pattern?
I assume your DataGrids ItemsSource is bound to an Collection of Log4NetLog's, so you can do styling in xaml:
<DataGrid.ItemContainerStyle>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="Background" Value="{Binding Path=LogColour.Colour}"/>
</Style>
</DataGrid.ItemContainerStyle>
Maybe you need a Color to SolidColorBrush Converter.
I want to make it so a double click is required to select an item in a ListBox. This selected item should always be bold. I know the SelectedItem property will no longer reflect the item I am treating as the selected item, so the XAML below that I was previously using to make the selected item bold will no longer work.
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
I have looked into how to handle a double click with MVVM and have concluded that it is ok to use code behind and the MouseDoubleClick event.
private void lbProfiles_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
_viewModel.SelectedProfile = ((ListBox)sender.)SelectedItem as MyProfile;
//What should go here?
}
My view model will have a SelectedProfile property that I think will be set in the method above. Is there anyway to bind SelectedProfile in XAML or will it have to be managed in the code behind? Also, what is the best way to make this item bold?
Edit 1:
I ended up tweaking Rachel's answer a little so that on a single click the item is highlighted but not selected. That way the view model can have a SelectedItem property and a HighlightedItem property.
private void ListBoxItem_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ClickCount < 2)
e.Handled = true;
var clickedItem = ((ContentPresenter)e.Source).Content as MyProfile;
if (clickedItem != null)
{
//Let view model know a new item was clicked but not selected.
_modelView.HighlightedProfile = clickedItem;
foreach (var item in lbProfiles.Items)
{
ListBoxItem lbi =
lbProfiles.ItemContainerGenerator.ContainerFromItem(item) as ListBoxItem;
//If item is not displayed on screen it may not have been created yet.
if (lbi != null)
{
if (item == clickedItem)
{
lbi.Background = SystemColors.ControlLightBrush;
}
else
{
lbi.Background = lbProfiles.Background;
}
}
}
}
}
The easiest way to select an item on DoubleClick only is to mark the click event as Handled if the ClickCount is less than 2
This would also allow you to keep your Trigger that sets the text as Bold when it's selected
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter Event="PreviewMouseDown" Handler="ListBoxItem_PreviewMouseDown" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
private void ListBoxItem_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ClickCount < 2)
e.Handled = true;
}
Just keep in mind that this disables all single click events on the ListBoxItem. If you want to allow some single-click events, you'll have to adjust the PreviewMouseDown event to not mark specific clicks as Handled.
How to "Set" the SelectedIndex in DevExpress ComboBoxEdit?
I tried both in XAML and in code behind, but the index was not set, it starts out with a blank item.
My XAML: [I can't see why this doesn't work, but it doesn't..]
<dxb:BarEditItem.EditSettings>
<dxe:ComboBoxEditSettings>
<dxe:ComboBoxEditSettings.Items>
<dxe:ComboBoxEditItem IsSelected="True">AAA</dxe:ComboBoxEditItem>
<dxe:ComboBoxEditItem>BBB</dxe:ComboBoxEditItem>
<dxe:ComboBoxEditItem>CCC</dxe:ComboBoxEditItem>
</dxe:ComboBoxEditSettings.Items>
</dxe:ComboBoxEditSettings>
</dxb:BarEditItem.EditSettings>
My C# code: [I'm getting the countStr correctly so I'm sure the ComboBoxEdit and the items are initialized and added ok, but SelectedIndex still don't set the index..]
* also I do not want to use EditValue to set the value, I need to use an integer (Index) to set it.
private void Foo_LinkControlLoaded(object sender,
DevExpress.Xpf.Bars.BarItemLinkControlLoadedEventArgs e)
{
BarEditItemLink link = (BarEditItemLink)sender;
countStr = ((ComboBoxEdit)link.Editor).Items.Count.ToString();
((ComboBoxEdit)link.Editor).SelectedIndex = 2;
}
There are no SelectedIndex or SelectedItem property within the editor settings (e.g. ComboBoxEditSettings).
But you can set the SelectedIndex, SelectedItem or EditValue properties of ComboBoxEdit via the editor style:
<dxb:BarEditItem x:Name="beiComboBox">
<dxb:BarEditItem.EditStyle>
<Style TargetType="dxe:ComboBoxEdit">
<Setter Property="SelectedIndex" Value="1"/>
</Style>
</dxb:BarEditItem.EditStyle>
<dxb:BarEditItem.EditSettings>
<dxe:ComboBoxEditSettings>
<dxe:ComboBoxEditSettings.Items>
<dxe:ComboBoxEditItem>AAA</dxe:ComboBoxEditItem>
<dxe:ComboBoxEditItem>BBB</dxe:ComboBoxEditItem>
<dxe:ComboBoxEditItem>CCC</dxe:ComboBoxEditItem>
</dxe:ComboBoxEditSettings.Items>
</dxe:ComboBoxEditSettings>
</dxb:BarEditItem.EditSettings>
</dxb:BarEditItem>
You can also set a ComboBoxEdit.SelectedIndex property from codebehind if you catch the Loaded event:
<dxb:BarEditItem.EditStyle>
<Style TargetType="dxe:ComboBoxEdit">
<EventSetter Event="Loaded" Handler="ComboBoxEdit_Loaded"/>
</Style>
</dxb:BarEditItem.EditStyle>
//...
void ComboBoxEdit_Loaded(object sender, RoutedEventArgs e) {
((ComboBoxEdit)sender).SelectedIndex = 1;
}
I have a simple class:
public class Foo
{
public string Text { get; set; }
public bool AppleStyle { get; set; }
public Foo(string text, bool applyStyle)
{
Text = text;
ApplyStyle = applyStyle;
}
public override string ToString()
{
return Text;
}
}
Which is then used to add items to a ListBox:
var one = new Foo("Some Text", false);
var two = new Foo("More Text", true);
MyListBox.Items.Add(one);
MyListBox.Items.Add(two);
I then loop through the items in the ListBox to figure out how to style them. This is where I get stuck. I tried inheriting from ListBoxItem for the class, but no items get added if I do that.
for (int i = 0; i < MyListBox.Items.Count; i++)
{
if(((Foo)MyListBox.Items[i]).ApplyStyle)
{
((ListBoxItem)MyListBox.Items[i]).Style = Resources["MyStyle"] as Style;
}
}
Update:
In MainWindow.xaml:
<Window.Resources>
<Style x:Key="MyStyle" TargetType="ListBoxItem">
<Setter Property="Background" Value="Bisque"></Setter>
<Setter Property="FontWeight" Value="Bold"></Setter>
</Style>
</Window.Resources>
Update 3:
Making some progress, just need to know how to refresh the styles (after clicking on a button). Plus if Resource is not in MainWindow.xaml, would it then look in App.xaml before returning null?
MainWindow.xaml
<Window...>
<Window.Resources>
<Style x:Key="MyClass" TargetType="ListBoxItem">
<Setter Property="Background" Value="Bisque"></Setter>
<Setter Property="FontWeight" Value="Bold"></Setter>
</Style>
<myapp:MyListItemStyleSelector x:Key="MyListItemStyleSelector" />
</Window.Resources>
<Grid>
...
<ListBox .... ItemContainerStyleSelector="{StaticResource: MyListItemStyleSelector}" />
...
</Grid>
</Window>
MyListItemStyleSelector.cs
public class MyListItemStyleSelector : StyleSelector
{
public override Style SelectStyle(object item, DependencyObject container)
{
ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(container);
int index = ic.ItemContainerGenerator.IndexFromContainer(container);
Style applyStyle = null;
var data = item as Foo;
if (data != null && data.ApplyStyle)
{
applyStyle = ic.TryFindResource("MyStyle") as Style;
}
return applyStyle;
}
}
I think you have some sort of mixup here, i try to explain as good as i can.
First of all You usually never need to change the Style in code, like your last code block.
One thing that is difficult to understand in the beginning is the use of a ItemContainerStyle and DataTemplate.
I would suggest that you do the following.
Instead of changing the style off your ListBoxItem see if it is sufficient to use a DataTemplate. The DataTemplate defines how the Content of your ListBoxItem is shown.
<DataTemplate TargetType="{x:Type Foo}">
<!-- your visuals and controls here -->
</DataTemplate>
Now if you want to use different datatemplates you could use different classes and create different DataTemplates for them, or you use a DataTemplateSelector
public class FooTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
var mdl = item as Foo;
if( mdl.AppleStyle )
return element.FindResource("appleTemplate") as DataTemplate;
return element.FindResource("normalTemplate") as DataTemplate;
}
}
Create that templateselector in xaml and reference it in your listbox
<myNs:FooTemplateSelector x:Key="fooTemplateSelector"/>
<Listbox DataTemplateSelector="{StaticResource fooTemplateSelector}"/>
now you need to create 2 DataTemplates appleTemplate *normalTemplate* and you can easyl distinguish which data template to use vial the selector. Which is done automatically in the ListBox for you.
If you really want to change the Style of the ItemContainer you can use ItemContainerStyleSelector which works similar to the DataTemplateSelector. But i would not suggest it. You should supply the content and leave the ListBoxItem as it is, only if you want to modify the design(in this case, the selection color etc.), otherwise it might confuse the user or break functionality.
If you add data-objects directly to the ListBox the container-items will be generated automatically, you cannot get them this way.
Use the ItemContainerGenerator:
((ListBoxItem)MyListBox.ItemContainerGenerator.ContainerFromIndex(i)).Style = Resources["MyStyle"] as Style;
Why not do this in the XAML?
<ListBox Name="MyListBox">
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding ApplyStyle}" Value="True">
<Setter Property="Background" Value="Bisque" />
<Setter Property="FontWeight" Value="Bold" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
</ListBox>
But your overall problem is that ListBox.Items returns a collection of data objects, not XAML Controls. To get the XAML control that contains the Data Object you have to do as H.B. suggested and use MyListBox.ItemContainerGenerator.ContainerFromItem(dataObject) to get the XAML Container for the data object. Just be sure you wait until after the ItemContainerGenerator has finished rendering items to get the container (I believe it has a Status property or StatusChanged event you can use... it's been a while and I can't remember the exact syntax)